Java基础

您所在的位置:网站首页 drawstring 乱码 Java基础

Java基础

2023-04-06 22:15| 来源: 网络整理| 查看: 265

第一阶段

第一阶段 目标:建立编程思想

Java就业方向

Java EE软件工程师:电商、团购、众筹、sns、教育、金融、搜索 大数据软件工程师:大数据引用工程师、大数据算法工程师、大数据分析和挖掘 Android软件工程师:android开发工程师 1、Java概述

写第一个Java小程序

创建txt文本,写入如下内容,并保存为Helloworld.java

public class Helloworld { public static void main(String[] args) { System.out.println("Hello World!!!"); } }

进入Helloworld.java的保存路径,打开cmd

在DOS窗口中输入:

javac Helloworld.java

继续输入

java Helloworld

显示结果

Hello World!!! 1.1、Java历史 1990,sun公司 启动绿色计划 1991,创建 oak(橡树)语言,后发现 oak已被注册,看见咖啡上冒热气—>改名为:Java 1994,gosling 参加 硅谷大会 演示 Java 功能,震惊世界 1995,sun 公司正式发布 Java 1.0版本 2009年,甲骨文公司(Oracle 公司)宣布收购 sun 公司 2011年,甲骨文公司(Oracle 公司)发布 Java 7 2014年,甲骨文公司(Oracle 公司)发布 Java 8(长期支持版) JDK 8和 JDK 11是社会上最主流的版本 Oracle官网:https://www.oracle.com/ 其他JDK版本如下:

Java技术体系平台

Java SE(Java Standard Edition) 标准版

支持面向桌面级应用(Windows下的应用程序)的Java平台,提供了完整的Java核心API,此版本以前称为 J2SE。

Java EE(Java Enterprise Edition) 企业版

开发企业环境下的应用程序提供的一套解决方案。该技术体系中包含:Servlet、JSP等,主要针对于Web应用开发,此版本之前为J2EE。

Java ME(Java Micro Edition) 小型版

支持Java程序运行在移动端(手机、PDA)上的平台,对Java API有所精简,并加入了针对移动端的支持,此版本之前为 J2ME。

1.2、Java特点

面向对象(OOP)

健壮性。Java的强类型机制、异常处理、垃圾的自动收集等是Java程序健壮性的重要保证。

跨平台性。即:一个编译好的.class文件可以在多个系统下运行(只需要对应系统下安装对应的虚拟机),这种特性称为跨平台性。

解释型语言(JS、PHP、Java),编译型语言:C/C++

解释型语言:编译后的代码,不能被机器执行,需要解释器来执行 编译型语言:编译后的代码,可以直接被机器执行

1.3、Java运行机制

Java 核心机制——Java 虚拟机(JVM,Java Virtual Machine)

1)JVM 是一个虚拟的计算机,具有指令集并使用不同的存储区域。负责执行指令、管理数据、内存、寄存器,包含在JDK中。

2)对于不同的操作系统,有不同的虚拟机。

3)Java虚拟机机制屏蔽了底层运行平台的差别,实现了“一次编译,到处运行”。

以Test.java文件运行为例

Java运行机制(⭐⭐⭐⭐⭐)

注:.java文件也叫源文件,.class文件也叫字节码文件。

运行的本质就是把字节码文件加载到JVM中执行。

1.4、JDK 和 JRE

JVM基本介绍:

JVM全称:Java Virtural Machine,Java虚拟机 只认识xxx.class文件,jvm只负责将.class文件中的字节码指令进行识别并调用的操作系统向上的API完成动作。所以,JVM是Java能够跨平台的核心。即JVM能够跨平台 -> Java能跨平台。 边解释边执行.class字节码文件,会把字节码文件中的指令翻译成对应操作系统的机器码。

JDK基本介绍:

JDK全称:Java Development Kit,Java开发工具包

JDK = JRE + Java的开发工具(Java,javac,javadoc,javap等)

JDK是提供给Java开发人员用的,包含了Java的开发工具,也包括了JRE。

JRE基本介绍:

JRE全称:Java Runtime Environment ,Java运行时环境

JRE = JVM + Java的核心类库(类)

包括Java虚拟机(JVM)和Java程序所需的核心类库等,如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。

JDK、JRE 和 JVM 的包含关系?(⭐⭐⭐⭐)

$JDK = JRE + 开发工具集(javac,java编译工具等)$

$JRE = JVM + Java SE 标准类库(java核心类库)$

$JDK = JVM + Java SE 标准类库 + 开发工具集$

如果只想运行编译好的.class文件,只需要JRE。

JDK的安装

Oracle官网下载JDK:Java Downloads | Oracle

选择对应版本的JDK安装程序

配置环境变量

右键打开【我的电脑】—>【属性】—>【高级系统设置】—>【高级】—>【环境变量】 在【系统变量】下,新建变量JAVA_HOME,路径为JDK安装路径。如:D:\Program Files (x86)\JavaJDK8 编辑【系统变量】中的Path环境变量,【新建】添加路径:%JAVA_HOME%\bin 打开DOS命令行(cmd),任意目录下敲入:java 或者 javac,出现参数信息,配置成功

注:JDK安装路径不要有中文字符或特殊符号(如空格)等。

1.5、快速入门

使用DOS命令行编译源文件和运行Java程序,需要使源文件的编码格式和DOS命令行的代码页保持一致。否则会出现乱码问题。

如:源文件编码格式为GBK,则DOS命令行的代码页为936。

如图:

什么是编译?

即:javac Hello.java

有了Java源文件,通过编译器将其编译成JVM可以识别的字节码文件。 在该源文件目录下,通过javac.exe编译工具对Hello.java文件进行编译。本质就是将.class文件加载到 JVM 执行。 如果程序没有错误,没有任何提示,则在当前目录下会出现一个Hello.class文件,该文件称为字节码文件,也是可以执行的Java程序。

注:对于修改后的Java源文件,需要使用javac命令重新编译和运行才能使修改部分生效。

一个源文件中最多只能有一个public 类,其他类的个数不限。可以将main方法写在非 public 类中,然后指定运行非 public 类,这样入口方法就是非 public 的 main方法。

如果源文件包含一个 public 类,则文件名必须以该类名命名。

常见错误

找不到文件

解决方法:源文件名不存在或者写错,或者当前路径错误

类XXX是公共的,请在文件中说明

解决方法:声明为 public 的主类应与文件名一致,否则编译失败

编译失败

解决方法:注意错误出现的行数,再到源代码中指定位置改错

无法映射字符

解决方法:修改源文件的编码格式为GBK或者将DOS窗口的编码格式改为UTF-8

1.6、转义字符

\t:一个制表位,实现对齐的功能

\n:换行符

\\:一个\

\":一个“

\’:一个’

\r:一个回车

1.7、Java开发规范 类、方法的注释,要以javadoc的方式来写。 非Java Doc的注释(单行注释和多行注释),往往是给代码的维护者看的,着重告诉读者为什么这样写,如何修改,注意什么问题等。 使用Tab操作,实现缩进,默认整体向右边移动,使用shift + tab整体向左移。 运算符和 = 两边加一个空格,增加代码可读性。 源文件使用UTF-8编码。 行宽度不要超过80个字符。 代码编写的两种风格:次行风格和行尾风格(推荐)。 1.8、Java API 文档

JDK 8在线官方文档:Java Platform Standard Edition 8 Documentation (oracle.com)

1.9、Java注释

用于注释解释程序的文字就是注释,注释提高了代码的可读性;注释是一个程序猿必须要具有的良好编程习惯,将自己的思想通过注释写出来,再用代码实现。Java中的注释类型:

单行注释(快捷键:Ctrl + /)

// 单行注释

多行注释

/** * 多行注释 * 多行注释 */

文档注释

/** 文档注释 * @author Yxz * @version 1.0 * @Description TODO * @date 2021-07-30 16:39 */ 1.10、DOS命令

Dos介绍:Disk Operating System 磁盘操作系统。

相对路径:从当前目录开始定位,形成的一个路径。

绝对路径:从顶级目录C盘或D盘等开始定位,形成的路径。如:D:\Program

常用Dos命令(⭐⭐⭐⭐):

查看当前目录的所有内容:dir,如:dir d:\java 切换到其他盘:cd 盘符号,如:切换到C盘cd /D c: 切换到当前盘的其他目录下:使用cd 相对路径(或绝对路径)。如:cd d:\java\JavaApplication 切换到上一级:cd ..,如:cd ..\ 切换到根目录:cd \ 查看指定文件夹的所有子级目录:tree 路径。如:tree d:\java 清屏操作:cls 退出Dos:exit 创建目录:md。如:在当前文件夹下创建test100文件夹和test200文件夹,md test100 test200 删除目录:rd。如:删除当前文件夹下的test100目录:rd test100 拷贝文件:copy。如:将文件test2.txt复制到D盘Java文件目录下并且命名为temp.txt,copy test2.txt d:java\temp.txt 删除文件:del,如:删除文件test2.txt,del test2.txt 输入内容到文件:echo。将内容输入到文件里,如:echo hello > test.txt,将hello输入到test.txt文件里。 输入空文件:type。可以将空内容输入到文件里,如:type nul > test.txt,test.txt文件里为空。 剪切:move。将文件移动到某个文件目录下,move test.txt ..\test2\ok.txt,将文件test.txt移动到父目录下的test2文件下并命名为ok.txt 1.11、如何学习新技术 需求:工作需要、跳槽(对方要求)、技术控 看看能否使用传统技术解决 引出我们学习的新技术和知识点 学习新技术或者知识点的基本原理和基本语法(不要考虑细节) 快速入门:能写出基本程序,实现CRUD(增删改查) 开始研究技术的注意事项、使用细节、使用规范、如何优化 ——> 学无止境,技术的魅力 2、变量 2.1、数据类型

基本数据类型

数值型

整数类型:byte[1],short[2],int[4],long[8]——用于存放整数值 浮点(小数)类型:float[4],double[8]——用于存放小数

字符型:char[2]——存放单个字符

java中char的本质是一个整数,输出时是对应unicode码对应的字符。char类型可以进行运算。

存储时:‘a’ ——> 码值97 ——> 二进制 ——> 存储

读取时:二进制 ——> 97 ——> ‘a’ ——> 显示

布尔型boolean[1],存放true或false

引用数据类型

类class 接口interface 数组[]

常见字符编码表

ASCII码(1个字节表示,一共表示128个字符,实际上一个字节可以表示256个字符,ASCII码只占用了一个字节的后面七位,最前面一位统一规定为0)

Unicode码(固定大小编码,使用2个字节表示字符,字母和汉字统一两个字节存储,浪费存储空间.使用Unicode没有乱码问题,可以表示65536个字符,编码0-127的字符与ASCII码一样,Unicode码兼容ASCII码)

UTF-8码(改进的Unicode码,大小可变的编码,字母使用1个字节,汉字使用3个字节。可以使用1-6个字节表示符号,根据不同符号变化字节长度)

GBK(可以表示汉字,范围广,字母使用1个字节,汉字使用2个字节)

GB2312(可以表示汉字,GB2312 < GBK)

BIG5码(存储繁体中文)

布尔型:boolean[1],存放 true 和 false 。一般用于流程控制。

public class PrimitiveTypeTest { public static void main(String[] args) { // byte System.out.println("基本类型:byte 二进制位数:" + Byte.SIZE); System.out.println("包装类:java.lang.Byte"); System.out.println("最小值:Byte.MIN_VALUE = " + Byte.MIN_VALUE); System.out.println("最大值:Byte.MAX_VALUE = " + Byte.MAX_VALUE); System.out.println(); // short System.out.println("基本类型:short 二进制位数:" + Short.SIZE); System.out.println("包装类:java.lang.Short"); System.out.println("最小值:Short.MIN_VALUE = " + Short.MIN_VALUE); System.out.println("最大值:Short.MAX_VALUE = " + Short.MAX_VALUE); System.out.println(); // int System.out.println("基本类型:int 二进制位数:" + Integer.SIZE); System.out.println("包装类:java.lang.Integer"); System.out.println("最小值:Integer.MIN_VALUE = " + Integer.MIN_VALUE); System.out.println("最大值:Integer.MAX_VALUE = " + Integer.MAX_VALUE); System.out.println(); // long System.out.println("基本类型:long 二进制位数:" + Long.SIZE); System.out.println("包装类:java.lang.Long"); System.out.println("最小值:Long.MIN_VALUE = " + Long.MIN_VALUE); System.out.println("最大值:Long.MAX_VALUE = " + Long.MAX_VALUE); System.out.println(); // float System.out.println("基本类型:float 二进制位数:" + Float.SIZE); System.out.println("包装类:java.lang.Float"); System.out.println("最小值:Float.MIN_VALUE = " + Float.MIN_VALUE); System.out.println("最大值:Float.MAX_VALUE = " + Float.MAX_VALUE); System.out.println(); // double System.out.println("基本类型:double 二进制位数:" + Double.SIZE); System.out.println("包装类:java.lang.Double"); System.out.println("最小值:Double.MIN_VALUE = " + Double.MIN_VALUE); System.out.println("最大值:Double.MAX_VALUE = " + Double.MAX_VALUE); System.out.println(); // char -> 单一的 16 位 Unicode 字符 System.out.println("基本类型:char 二进制位数:" + Character.SIZE); System.out.println("包装类:java.lang.Character"); // 以数值形式而不是字符形式将Character.MIN_VALUE输出到控制台 System.out.println("最小值:Character.MIN_VALUE = " + (int) Character.MIN_VALUE); // 以数值形式而不是字符形式将Character.MAX_VALUE输出到控制台 System.out.println("最大值:Character.MAX_VALUE = " + (int) Character.MAX_VALUE); } } /* 运行结果: 基本类型:byte 二进制位数:8 包装类:java.lang.Byte 最小值:Byte.MIN_VALUE = -128 最大值:Byte.MAX_VALUE = 127 基本类型:short 二进制位数:16 包装类:java.lang.Short 最小值:Short.MIN_VALUE = -32768 最大值:Short.MAX_VALUE = 32767 基本类型:int 二进制位数:32 包装类:java.lang.Integer 最小值:Integer.MIN_VALUE = -2147483648 最大值:Integer.MAX_VALUE = 2147483647 基本类型:long 二进制位数:64 包装类:java.lang.Long 最小值:Long.MIN_VALUE = -9223372036854775808 最大值:Long.MAX_VALUE = 9223372036854775807 基本类型:float 二进制位数:32 包装类:java.lang.Float 最小值:Float.MIN_VALUE = 1.4E-45 最大值:Float.MAX_VALUE = 3.4028235E38 基本类型:double 二进制位数:64 包装类:java.lang.Double 最小值:Double.MIN_VALUE = 4.9E-324 最大值:Double.MAX_VALUE = 1.7976931348623157E308 基本类型:char 二进制位数:16 包装类:java.lang.Character 最小值:Character.MIN_VALUE = 0 最大值:Character.MAX_VALUE = 65535 */

注意:

Java各整数类型有固定的范围和字段长度,不受具体OS【操作系统】的影响,以保证Java程序的可移植性。 Java整型常量默认为int型,声明long类型常量须后加l或L。 Java程序中变量常声明为int型,除非int型范围不够才使用long。 bit:计算机中的最小存储单位。 byte(Byte):计算机中基本存储单元。 1 Byte = 8 bit 浮点数在机器中存放形式:浮点数 = 符号位 + 指数位 + 尾数位。 尾数部分可能丢失,造成精度损失(小数都是近似值)。 Java浮点型表示的范围和字段长度不受OS的影响(float 4个字节,double 8个字节)。 Java的浮点型常量默认为double型,声明float型常量须后加f或F。 浮点数常量有两种表示形式。如:5.12、10.1、.131(=0.131) 通常情况下,应该使用double型,因为它比float型精度更高。 不要对运算后的小数进行相等判断(小数运算后会出现精度丢失) 2.2、变量基本使用

变量是程序的基本组成单位。变量的三个基本要素:类型 + 名称 + 值。

public class Var01 { public static void main(String[] args) { int a; // 声明一个整型变量a a = 100; // 给变量a赋值 System.out.println(a); int b = 800; // 声明一个变量b并初始化赋值800 System.out.println(b); } }

注意:

变量表示内存中的一个存储区域(不同的变量,类型不同,占用的空间大小不同)。 该区域有自己的名称(变量名)和类型(数据类型)。 变量必须先声明,后使用。 该区域的数据可以在同一类型范围内不断变化。 变量在同一个作用域内不能重名。 变量 $=$ 数据类型 $+$ 变量名 $+$ 变量值。 2.3、数据类型转换

自动类型转换

当Java程序在进行赋值或者运算时,精度小(容量小)的类型自动转换为精度大(容量大)的数据类型。

数据类型按精度(容量)大小排序:

char ——> int ——> long ——> float ——> double

byte ——> short ——> int ——> long ——> float ——> double

注:

有多种类型的数据混合运算时,系统先自动将所有数据转成容量最大的那种数据类型,然后再进行计算。 将具体数值赋给变量前,须先判断是否超过变量类型的存储范围。 如果是变量类型赋值,会直接判断类型是否超出。 byte、short、char之间不会相互自动转换。 byte、short、char三者可以计算,在计算时首先转换成int型(与是否混合运算无关)。 boolean不参与转换。 表达式结果的类型自动提升为操作数中最大的类型。 public class AutoConvert { public static void main(String[] args) { int i = 1; // 正确 int b1 = 10; float b2 = b1 + 1.1; // 报错1:1.1默认为double型,num1+1.1为double型,不能兼容float // 改进方法1:float b2 = b1 + 1.1F; // 改进方法2:double b2 = b1 + 1.1; byte c1 = 128; // 错误2:超出byte表示范围[-128, 127] int d1 = 1; byte d2 = d1; // 报错3:变量类型赋值,会直接判断类型是否超出接受类型的大小。不兼容的类型 byte e1 = 10; char e2 = e1; // 报错4:byte、short、char之间不会相互自动转换,byte不能自动转换成char byte b = 1; short s1 = 1; short s2 = b + s1; // 报错5:b + s1 => 结果为int型,short不能兼容int型 // 改进方法:short s2 = b + s1; byte b3 = 2; byte b4 = b + b3; // 报错5:有byte参与运算,结果自动提升为int型,byte不能兼容 boolean pass = true; int i2 = pass; // 报错6:boolean不参与类型的自动转换 byte b5 = 1; short s3 = 100; int num200 = 2; double num300 = 1.1; int res = b5 + s3 + num200 + num300; // 报错7:表达式中最高精度为double型num300,结果为double型,int不能兼容 // 改进方法1;double res = b5 + s3 + num200 + num300; // 改进方法2;float res = b5 + s3 + num200 + 1.1F // 改进方法3;double res = b5 + s3 + num200 + 1.1F; } }

强制类型转换

自动类型转换的逆过程。将容量大的数据类型转换为容量小的数据类型。使用时要加上强制转换符(),但有可能造成精度降低或溢出

注意:

数据的从大——>小,用强转。 强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级。 char类型可以保存int型的常量值,但不能保存int型的变量,需要强转。 public class ForceCOnvert { public static void main(String[] args) { int n1 = (int)1.9; SYstem.out.println("n1 = " + n1); // 精度损失 int n2 = 2000; byte b1 = (byte)n2; SYstem.out.println("b1 = " + b1); // 数据溢出:2000超出byte范围[-128, 127] int x = (int)10 * 3.5 + 6 * 1.5; // 编译错误2:double ——> int不兼容,精度丢失 // 强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级 SYstem.out.println(x); // 改进方法:int x = (int)(10 * 3.5 + 6 * 1.5); char c1 = 100; // 正确,char能保存int型常量值 int m = 100; // 正确 char c2 = m; // 错误3:char不能保存int型的变量 char c3 = (char)m; // 正确,char保存int型变量需要强转 System.out.println(c3); // 输出(100对应的字符)d } }

基本数据类型和String类型的转换

基本类型转String类型

语法: 将基本类型的值+“”即可

public class StringToBasic { public static void main(String[] args) { int n1 = 100; float n2 = 1.1f; double n3 = 3.14; boolean b1 = true; String str1 = n1 + ""; String str2 = n2 + ""; String str3 = n3 + ""; String str4 = b1 + ""; System.out.println(str1 + " " + str2 + " " + str3 + " " + str4); } }

String类型转基本数据类型

语法:通过基本类型的包装类调用parseXXX方法即可

注意:

在将String类型转换成基本数据类型时,要确保String类型能够转成有效的数据。 如果格式不正确,就会抛出异常,程序就会终止。 public class BasicToString { public static void main(String[] args) { String s5 = "123"; int num1 = Integer.parseInt(s5); double num2 = Double.parseDouble(s5); float num3 = Float.parseFloat(s5); long num4 = Long.parseLong(s5); byte num5 = Byte.parseByte(s5); boolean b = Boolean.parseBoolean("true"); short num6 = Short.parseShort(s5); // 将String类型转换成char类型,在字符串中将指定位置的字符传换成char char c = s5.charAt(0); String str = "hello"; int n1 = Integer.parseInt(str); // 抛出异常 System.out.println(n1); } } 3、运算符 3.1、运算符介绍

运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。

3.2、算术运算符

算数运算符(ArithmeticOperator)是对数值类型的变量进行运算。

运算符 运算 范例 结果 $+$ 正号 +7 7 $-$ 负号 b = 11; -b -11 $+$ 加 9 + 9 18 $-$ 减 10 - 5 5 $*$ 乘 7 * 8 56 $/$ 除 9 / 4 2 % 取模(取余) 10 % 3 1 ++ 自增(前) a = 2; b = ++a; a = 3; b = 3 ++ 自增(后) a = 2; b = a++; a = 3; b = 2 -- 自减(前) a= 2; b = --a; a = 1; b = 1 -- 自减(后) a= 2; b = a--; a = 1; b = 2 + 字符串连接 “study ” + “java” “study java”

注意

除法结果类型为操作数的最高精度。

取模(%)本质:a % b = a - a / b * b。

a % b当a是小数时,公式:a % b = a - (int)a / b * b。

有小数的运算,运算的结果是近似值。

Java语言中 + 的使用:

当左右两边都是数值型时,则作加法运算 当左右两边有一方为字符串,则作拼接运算 运算顺序:从左至右 public class ArithmeticOperator { public static void main(String[] args){ System.out.println(10 / 4); // 2 System.out.println(10.0 / 4); // 2.5 // 取模(取余) System.out.println(10 % 3); // 1 = 10 - 10 / 3 * 3 System.out.println(-10 % 3); // -1 = (-10) - (-10) / 3 * 3 System.out.println(10 % -3); // 1 = 10 - 10 / (-3) * (-3) System.out.println(-10 % -3); // -1 = (-10) - (-10) / (-3) * (-3) System.out.println(-10.5 % 3); // -1.5 = -10.5 - (int)(-10.5) / 3 * 3, 结果为-1.5的近似值 System.out.println(-10.4 % 3); // -1.4 = -10.4 - (int)(-10.4) / 3 * 3, 结果为-1.4的近似值 } } 3.3、关系运算符

关系运算符(RelationalOperator)的结果都是boolean型,要么是true,要么是false。

运算符 运算 范例 结果 $==$ 相等于 $8 == 7$ false $!=$ 不等于 $8 != 7$ true $$ 大于 $8 > 7$ true $= 7$ true instance of 检查是否是类的对象 “yxz” instance of String true public class RelationalOperator { public static void main(String[] args) { int a = 9; int b = 8; System.out.println(a > b); System.out.println(a < b); System.out.println(a >= b); System.out.println(a 20 && age < 90) { System.out.println("ok100"); } // &逻辑与 if(age > 20 & age < 40) { System.out.println("ok200"); } //区别 int a = 4; int b = 9; if(a < 1 && ++b < 50) { System.out.println("ok300"); } System.out.println("a = " + a + "b = " + b); // 输出a = 4,b = 9 if(a < 1 & ++b < 50) { System.out.println("ok300"); } System.out.println("a = " + a + "b = " + b); // 输出a = 4,b = 10 } } 3.5、赋值运算符

赋值运算符(AssignOperator)就是将某个运算后的值,赋给指定的变量。

分类

基本赋值运算符= 复合赋值运算符+=,-=,*=,/=,%=等。先运算后赋值

特点

运算顺序从右往左 赋值运算符的左边只能是变量,右边可以是变量、表达式、常量值。 复合赋值运算符会进行类型转换。 public class AssignOperator { public static void main(String[] args) { int n1 = 10; n1 += 4; // 等价于:n1 = n1 + 4; System.out.println(n1); // 14 n1 /= 3; // 等价于:n1 = n1 / 3; System.out.println(n1); // 4 // 复合赋值运算符会进行强制类型转换 byte b = 3; b += 2; // 等价于:b = (byte)(b + 2); b++; // 等价于:b = (byte)(b + 1) } } 3.6、三元运算符

三元运算符(TernaryOperator)基本语法:条件表达式 ? 表达式1 : 表达式2;

如果条件表达式为true,运算后的结果是表达式1;如果条件表达式为false,运算后的结果是表达式2.

表达式1和表达式2要为可以赋给接受变量的类型(或可以自动转换)。

三元运算符可以转成if-else语句

public class TernaryOperator { public static void main(String[] args) { int a = 30; int b = 50; int result = a > b ? a++ : b--; System.out.println(result) // 50 } } 3.7、运算符的优先级

表达式运算中的运算顺序。如下表,上一行运算符总优先于下一行。

只有单目运算符、赋值运算符是从右向左运算的。

. () {} ; , $R->L$ ++ -- ~ ! $L->R$ * / % $L->R$ + - $L->R$ >>> 位移 $L->R$ = instanceof $L->R$ == != $L->R$ & $L->R$ ^ $L->R$ ` $L->R$ && $L->R$ ` $L->R$ ? : $R->L$ = *= /= %= += -= = >>>= &= ^= `

标识符的命名规则和规范

Java中对各种变量、方法和类等命名时使用的字符序列成为标识符。 凡是可以自己起名字的地方都叫标识符。如:int num1 = 90; 命名规则(必须遵守): 由``26个英文字母大小写,0-9,_或$`组成。 数字不可以开头。 不可以使用关键字和保留字,但能包含关键字和保留字。 Java中严格区分大小写,长度无限制。 标识符不能含有空格。

标识符的规范

包名:多单词组成时所有字母都小写。如:com.yxz.dao 类名、接口名:多单词组成时,所有单词的首字母大写(大驼峰)。如:OperatorTest 变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写(小驼峰,驼峰法)。如:setNameBean 常量名:所有字母都大写。多单词时每个单词之间使用下划线连接。如:LENGTH_MAXSIZE [Java开发手册(嵩山版).pdf](file:D:/学习文件/Java开发手册(嵩山版).pdf)

关键字和保留字

关键字:被Java语言赋予了特殊含义,用作专门用途的字符串

特点:关键字中所有字母都为小写。

保留字:现有Java版本尚未使用,但以后版本可能会作为关键字使用。命名标识符时要避免使用。

如:byValue、cast、future、generic、inner、operator、outer、rest、var、goto、const

键盘输入

在编程中。需要接收用户输入的数据,可以使用键盘输入语句获取。需要一个扫描器(对象),就是Scanner类。

步骤:

导入该类的所在包,java.util.* 创建该类的对象(声明变量) 调用该类的成员方法 import java.util.Scanner; public class Input { public static void main(String[] args) { // 1.导入Scanner类所在包 // 2.创建Scanner对象,使用关键字 new 创建一个对象 Scanner scanner = new Scanner(System.in); // 3.接收用户输入,调用类的相关方法 System.out.println("请输入名字:"); String name = scanner.next(); // 等待接收键盘输入 System.out.println("请输入年龄:"); int age = scanner.nextInt(); // 等待接收键盘输入 System.out.println("请输入月薪:"); double salary = scanner.nextDouble(); // 等待接收键盘输入 System.out.println(name + "的个人信息"); System.out.println("年龄:" + age + "\t月薪:" + salary); } } 3.8、进制及转换

对于整数,有四种表示方式:

二进制:0,1。满2进1,以0b或0B开头。 十进制:0-9,满10进1。 八进制:0-7,满8进1,以数字0开头表示。 十六进制:0-9及A(10)-F(15),满16进1,以0x或0X开头表示。A-F不区分大小写。 public class BinaryTest { public static void main(String[] args) { // 二进制 int n1 = 0b1010; // 10 //十进制 int n2 = 1010; // 1010 // 八进制 int n3 = 01010; // 520 // 十六进制 int n4 = 0x10101; // 65793 } }

进制转换

二进制转十进制

规则:从最低位(右边)开始,将每个位上的数提取出来,乘以2的(位数-1)次方,然后求和。

如:$0b1011$ ——> $120+1*21+022+1*23 = 11$

八进制转十进制

规则:从最低位(右边)开始,将每个位上的数提取出来,乘以8的(位数-1)次方,然后求和。

如:$0234$ ——> $480+3*81+28^2 = 156$

十六进制转十进制

规则:从最低位(右边)开始,将每个位上的数提取出来,乘以16的(位数-1)次方,然后求和。

如:$0X23A$ ——> $10160+3*161+216^2 = 570$

十进制转二进制

规则:将该数不断除以2,直到商为0为止,然后将每步得到的余数倒过来,就是对应的二进制。

如:34 ——> 0b100010 ——> 不足一个字节(8位), 高位补0 ——> 0b00100010

十进制转八进制

规则:将该数不断除以8,直到商为0为止,然后将每步得到的余数倒过来,就是对应的八进制。

如:131 ——> 0203

十进制转十六进制

规则:将该数不断除以16,直到商为0为止,然后将每步得到的余数倒过来,就是对应的八进制。

如:237 ——> 0xED

二进制转八进制

规则:从低位开始,将二进制数每三位一组,转换成对应的八进制数即可。

如:0b11010101 ——> 0b11(3)010(2)101(5) ——> 0325

二进制转十六进制

规则:从低位开始,将二进制数每四位一组,转换成对应的十六进制数即可。

如:0b11010101 ——> 0b1101(D)0101(5) ——> 0XD5

八进制转二进制

规则:将八进制数每1位,转成对应的一个3位的二进制数即可。

如:0237 ——> 0(0b)2(010)3(011)7(111) ——> 0b010011111

十六进制转二进制

规则:将八进制数每1位,转成对应的一个4位的二进制数即可。

如:0x23B ——> 0x2(0010)3(0011)B(1011) ——> 001000111011

3.9、位运算符

Java中的位运算符

符号名称 位运算符 范例 结果 按位与 & 2 & 3 2 按位或 ` ` `2 按位取反 ~ ~2 -3 按位异或 ^ 2 ^ 3 1 算数左移 > 2 1 无符号右移(逻辑右移) >>> 16 >>> 2 4

运算规则:

算术右移>>:低位溢出,符号位不变,并用符号位补溢出的高位。 算数左移>:也叫无符号右移。低位溢出,高位补0。

原码、反码、补码

对于有符号数:

二进制的最高位是符号位:0表示正数,1表示负数。 正数的原码、反码、补码都一样(正数三码合一)。 负数的反码 $=$ 它的原码符号位不变 $+$ 其他位按位取反(0->1, 1->0)。 负数的补码 $=$ 它的反码$+$1,负数的反码 $=$ 负数的补码 $-$ 1。 0的反码、补码都是0。 Java中没有无符号数。 在计算机运算的时候都是以补码的方式运算的。 看运算结果时,要看它的原码。 public class BitOperator { public static void main(String[] args) { // 计算 2 & 3 = ? // 1. 2的原码:00000000 00000000 00000000 00000010 // 2的补码:00000000 00000000 00000000 00000010(正数三码合一) // 2. 3的原码:00000000 00000000 00000000 00000011 // 3的补码:00000000 00000000 00000000 00000011(正数三码合一) // 3. 2的补码 & 3的补码 // 00000000 00000000 00000000 00000010(2的补码) // 00000000 00000000 00000000 00000011(3的补码) // 00000000 00000000 00000000 00000010 是运算后的补码 // 4. 运算后的原码:00000000 00000000 00000000 00000010(正数三码合一) System.out.println(2 & 3); // 2 & 3 == 2 // 计算 ~-2 // 1. -2的原码:10000000 00000000 00000000 00000010(+2的原码基s础上,符号位改为1表示负数) // 2. -2的反码:11111111 11111111 11111111 11111101(原码基础上, 符号位不变, 其他位按位取反) // 3. -2的补码:11111111 11111111 11111111 11111110(反码基础上, +1) // 4. ~-2操作:00000000 00000000 00000000 00000001 是运算后的补码 // 5. 运算后的原码: 00000000 00000000 00000000 00000001(正数三码合一) System.out.println(~-2); // ~-2 == 1 // 计算 ~2 // 1. 2的补码:00000000 00000000 00000000 00000010(正数三码合一) // 2. ~2操作 :11111111 11111111 11111111 11111101 是运算后的补码 // 3. 运算后的反码:11111111 11111111 11111111 11111100(运算后的补码基础上, -1) // 4. 运算后的原码:10000000 00000000 00000000 00000011(运算后的反码基础上, 符号位不变, 其他位按位取反) System.out.println(~-2); // ~2 == -3 } } 4、控制结构 4.1、顺序控制

程序从上到下逐行地执行,中间没有任何判断和跳转。

Java中定义成员变量时采用合法的前向引用(先定义后使用)。

4.2、分支控制

单分支if

基本语法:

if (条件表达式) { 代码块;(可以多条语句) }

说明:当条件表达式为true时,就会执行{}内的代码。如果为false,就不执行。如果{}中只有一条语句,可以不写{},建议写上{}

双分支if-else

基本语法:

if (条件表达式) { 代码块1;(可以多条语句) } else { 代码块2;(可以多条语句) }

说明:当条件表达式为true时,就会执行代码块1,否则执行代码块2。

多分支if-else if

基本语法:

if (条件表达式1) { 代码块1;(可以多条语句) } else if (条件表达式2) { 代码块2;(可以多条语句) } else if (条件表达式3) { 代码块3;(可以多条语句) } ...... else { 代码块n;(可以多条语句) }

说明:当条件表达式1成立,则执行代码块1,如果不成立,判断条件表达式2,成立则执行代码块2,否则判断条件表达式3...,都不成立则执行else内的代码块。只能有一个执行入口。多分支可以没有else,如果所有条件表达式都不成立,则一个执行入口都没有。

选择分支switch-case

基本语法:

switch (表达式) { case 常量1: 语句块1; break; case 常量2: 语句块2; break; ...... case 常量n: 语句块n; break; default: 语句块; break; }

说明:表达式对应一个值,当表达式的值等于常量1,就执行语句块1,break表示退出switch,不再继续匹配。如果一个都没匹配上,执行default语句块。

注意:

表达式数据类型,应和case后的常量类型一致,或者是可以自动转成可以相互比较的类型。 switch(表达式)中表达式的返回值必须是:(byte、short、int、char、enum、String) case子句中的值必须是常量或者是常量表达式,不能是变量。 default子句是可选的,当没有匹配的case时,执行default。 break语句用来在执行完一个case分支后使程序跳出switch语句块;如果没有break,程序会顺序执行到switch结尾,除非遇到break。 4.3、循环控制

让一段代码可以循环重复的执行。

for循环

基本语法:

for (循环变量初始化; 循环条件; 循环变量迭代) { 循环体;(可以是多条语句) }

说明:

for关键字,表示循环控制。 for有四要素:(1)循环变量初始化(2)循环条件(3)循环操作(4)循环变量迭代。 循环条件是返回一个boolean值的表达式。 for(; 循环判断条件; )中的初始化和变量迭代可以写到其他地方,但是两边的;不能省略。 循环初始值可以有多条初始化语句,但是要求类型一样,并且中间用,隔开,循环变量迭代也可以有多条变量迭代语句,中间用,隔开。 public class ForCircle { public static void main(String[] args) { int count = 3; for (int i = 0, j = 0; i < count; i++, j += 2) { System.out.println("i = " + i + "j = " + j); } int sum = 0; int i = 1; for (; i a[j + 1]) { int temp = a[j + 1]; a[j + 1] = a[j]; a[j] = temp; } } // 输出排序后的数组 for (int i = 0; i < 5; i++) { System.out.print(a[i] + "\t"); } System.out.println("\n==============="); // 外层嵌套一层循环 for(int i = 0; i < 4; i++) { System.out.print("第" + (i + 1) + "轮\n"); for(int j = 0; j < 4 - i; j++) { if(a[j] > a[j + 1]) { int temp = a[j + 1]; a[j + 1] = a[j]; a[j] = temp; } } // 输出排序后的数组 for (int j = 0; j < 5; j++) { System.out.print(a[j] + "\t"); } System.out.print("\n"); } int[] arr = {10, 4, 3, 6, 8, 2, 7, 1, 0, 5, 9}; // 先死后活,将数组长度改成arr.length for(int i = 0; i < arr.length - 1; i++) { for(int j = 0; j < arr.length - 1 - i; j++) { if(arr[j] > arr[j + 1]) { int temp = arr[j + 1]; arr[j + 1] = arr[j]; arr[j] = temp; } } // 输出排序后的数组 for (int j = 0; j < arr.length; j++) { System.out.print(arr[j] + "\t"); } } } } 5.3、查找

Java中常用查找方法:

顺序查找(SequentialSearch)

import java.util.Scanner; public class SeqSearch { public static void main(String[] args) { String[] names = {"张三", "李四", "王五", "老六"}; Scanner scanner = new Scanner(System.in); System.out.print("请输入名字:"); String findName = scanner.next(); // 遍历数组,逐一比较 int index = -1; for (int i = 0; i < names.length; i++) { // 比较字符串 equals(String str) if (findName.equals(names[i])) { System.out.println("恭喜你找到 " + findName); System.out.println("下标为:" + i); index = i; // 保存下标 break; } } // 未找到则提示信息 if (index == -1) { System.out.println("sorry, 查无此人!"); } } }

二分查找(数据结构与算法中介绍)

6、面向对象编程(基础) 6.1、类与对象

类(class)是抽象的,概念的,代表一类事物,如:人类、猫类。即 类 ——> 数据类型

对象(object)是具体的,实际的,代表一个具体事物。即对象 ——> 实例

类是对象的模板,对象是类的一个个体,对应一个实例。

类包含属性(成员变量、字段、field)和行为(成员方法、类方法)。

属性(property)是类的一个组成部分,一般是基本数据类型,也可以是引用类型(对象、数组)

注意:

属性的定义语法和变量一致:访问修饰符 属性类型 属性名;,如:public String name;

Java一共有四种控制属性的访问范围,即以下四种访问修饰符:public、protected、默认、private。

属性的定义类型可以为任意类型,包含基本类型或引用类型。

属性如果不赋初值,有默认值,规则和数组一致。

JVM中对象的存储形式(⭐⭐⭐⭐⭐)

如何创建对象

先声明,再创建

基本语法:

类名 对象名; // 声明对象名,此时对象为空,没有创建对象空间 对象名 = new 类名(); // 创建对象(实例化)。new 会将创建的对象空间的地址返回赋给对象引用 Cat cat; // 声明对象名,此时cat为空,没有创建对象空间 cat = new Cat(); // 创建对象(实例化)。new 会将创建的对象空间的地址返回赋给cat

直接创建

基本语法:

类名 对象名 = new 类名(); // 声明对象名,同时创建对象空间并返回地址赋给对象名 Cat cat = new Cat(); // 类名Cat 对象引用(对象名)cat = 对象new Cat()

访问属性

基本语法:

对象名.属性名; cat.age;

Java类和对象的内存分配机制

栈:一般存放基本数据类型(局部变量)

堆:存放对象(自定义类、数组等)

方法区:常量池(常量,比如字符串等),类加载信息(只加载一次)

public class Person { // 类属性:姓名 String name; // 类属性:年龄 int age; // 类属性:薪水 double salary; // 类属性:通过考试 boolean isPass; // 默认构造方法 public Person() { } // main()方法 public static void main(String[] args) { Person p = new Person(); // p是对象引用(对象名), new Person()创建的对象空间(包括数据)才是真正的对象 p.name = "Yancy"; p.age = 22; p.salary = 10000.0; p.isPass = false; /* 1、先加载Person类信息(属性和方法信息,只加载一次) 2、在堆中分配空间,进行默认初始化 3、把对象在堆中地址返回给p 4、进行指定初始化,如:p.name = "Yancy" */ } } 6.2、成员方法

成员方法是对象的行为。

基本语法:

访问修饰符 方法返回值类型 方法名(形参列表) { 方法体; return 返回值; }

说明:

访问修饰符:控制方法的使用范围。 方法返回值类型:表示成员方法的输出,void表示没有返回值。返回值类型可以为任意类型(基本类型、引用类型)。 方法名:遵循驼峰命名法,见名知意。 形参列表:表示成员方法的输入。用于传入参数,有值传递和地址传递。参数类型可以为任意类型(基本类型、引用类型)。 方法体:实现某一功能的代码块。语句可以为输入、输出、变量、运算、分支、循环、方法调用,但是里面不能再定义方法。即方法不能嵌套定义。 return用于返回值,一个方法最多有一个返回值,返回值必须和返回值类型一致或兼容。如果是void,可以只写return;。 一个方法可以有0个参数,也可以有多个参数,中间用,隔开。 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数。 方法定义时的参数称为形式参数,简称形参;方法调用时的参数称为实际参数,简称实参。实参和形参的类型要一致或兼容,个数和顺序必须一致! 同一个类中的方法调用:直接调用即可,无需借助对象名。 跨类中的方法调用:需要通过对象名调用。跨类方法调用和方法的访问修饰符有关

注:当程序执行到方法时,就会开辟一个独立的空间(栈空间)。

成员方法的好处:

提高代码的复用性。 可以将实现的细节封装起来,然后供其他用户来调用即可。

示例1:

public class Method01 { String name; // speak 成员方法 // public 表示方法公开 // void : 表示方法没有返回值 // speak() : speak是方法名,()形参列表 // {} 方法体 :执行代码块 public void speak() { System.out.println("我是一个好人"); } }

示例2:

public class Method01 { String name; public static void main(String[] args) { Method01 method01 = new Method01(); method01.speak(); method01.cal01(); int returnRes = method01.cal02(100); System.out.println("0~100的计算结果 = " + returnRes); int[] res = method01.getSumAndSub(5, 3); System.out.println("和 = " + res[0] + "差 = " + res[1]); } // speak 成员方法 // public 表示方法公开 // void : 表示方法没有返回值 // speak() : speak是方法名,()形参列表 // {} 方法体 :执行代码块 public void speak() { System.out.println("我是一个好人"); B b = new B(); b.hi(); // 调用跨类方法需要借助对象名 } public void cal01() { int res = 0; for (int i = 0; i 地址传递(引用传递) // 遍历数组 System.out.println("main()方法的arr数组"); for(int i : arr) { // 加强for循环,遍历数组的常用操作。使用i接收数组arr中每个元素 System.out.print(i + "\t"); // 输出:985 211 6 } System.out.println(); } public void swap(int a, int b) { int temp = a; a = b; b = temp; } } class B { public void updateValue(int[] arr) { arr[0] = 985; // 修改元素 // 遍历数组 System.out.println("B类 update方法的arr数组"); for(int i : arr) { // 加强for循环,遍历数组的常用操作。使用i接收数组arr中每个元素 System.out.print(i + "\t"); // 输出:985 211 6 } System.out.println(); } }

克隆对象

通过地址传递的方法,克隆对象。

示例:

public class Person { String name; int age; public static void main(String[] args) { Person p = new Person(); p.name = "milan"; p.age = 22; MyTools tools = new MyTools(); Person p2 = tools.copyPerson(p); // p2的属性和p的属性相同,但是两个对象不等 System.out.println("p的名字:" + p.name + ", p的年龄:" + p.age); System.out.println("p2的名字:" + p2.name + ", p2的年龄:" + p2.age); // 检验两个对象是否相等(属性相等 + 对象空间地址相等) // 方法一:==判断 if(p == p2) { System.out.println("p和p2相等"); } else { System.out.println("p和p2不等"); } // 方法二:使用object自带的equals()方法判断 if(p.equals(p2)) { System.out.println("p和p2相等"); } else { System.out.println("p和p2不等"); } } } class MyTools { public Person copyPerson(Person p) { Person p2 = new Person(); p2.name = p.name; p2.age = p.age; return p2; } } 6.4、方法递归调用

递归(recursion)就是方法自己调用自己,每次调用时传入不同的变量。递归有助于编程解决复杂问题,可以让代码更简洁。

递归能解决什么问题?

各种数学问题:8皇后问题,汉诺塔,阶乘(factorial)问题,迷宫问题,球和篮子的问题 等等。 各种算法中用到递归:快速排序,归并排序,二分查找,分治算法 等等。 用栈解决的问题 ——> 递归代码更简洁。

示例:

public class Recursion { T t = new T(); t.test(4); // 输出:n = 2 n = 3 n = 4 int res = t.factorial(5); System.out.println("res = " + res); // 输出:res = 120 } class T { public void test(int n) { if (n > 2) { test(n - 1); } System.out.println("n = " + n); } // 计算n的阶乘n! public int factorial(int n) { if(n == 1) { return 1; } else { return factorial(n - 1) * n; } } }

递归重要规则

执行一个方法时,就创建一个新的独立空间(栈空间)。 方法的局部变量是独立的,不会相互影响。 如果方法中使用的是引用类型变量,就会共享该引用类型的数据。 递归三要素:递归函数的参数和返回值、递归终止条件、递归的等价关系式。递归必须向退出递归的条件逼近,否则会无限递归,抛出异常(StackOverFlowError, 栈溢出)。 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁。当方法执行完毕或返回时,方法占用的内存空间会被收回或释放。

迷宫问题(递归解决)

已知老鼠在地图最右上角,地图上有若干障碍物,现在需要找到一条路径使得老鼠能够走到地图右下角。规定老鼠每次只能走一格。

进阶思考:一共有多少条不同的路径数?最短路径?迷宫足够大时,递归爆栈,算法如何优化?

public class MiGong { public static void main(String[] args) { // 1. 先创建迷宫,用二维数组表示 int[][] map = new int[8][7]; int[][] map = new int[8][7]; // 2. 规定map数组的元素值: 0表示可以走,1表示障碍物 // 3. 最上面的一行和最下面的一行设置为1 for (int i = 0; i < 7; i++) { map[0][i] = 1; map[7][i] = 1; } // 4.最左边的一列和最右边的一列设置为1 for (int i = 0; i < 8; i++) { map[i][0] = 1; map[i][6] = 1; } // 5. 障碍物的位置设为1 map[3][1] = 1; map[3][2] = 1; map[2][2] = 1; // 输出当前地图 System.out.println("==========当前地图情况==========="); for (int[] value : map) { for (int i : value) { System.out.print(i + " "); } System.out.println(); } // 使用findWay找路 Solution solution = new Solution(); solution.findWay(map, 1, 1); // 传入老鼠的初始位置 System.out.println("\n=======找路的情况如下========="); for (int[] ints : map) { for (int anInt : ints) { System.out.print(anInt + " "); } System.out.println(); } } } class Solution { // 1. findWay()方法找到出迷宫的路径 // 2. 如果找到,返回true,否则返回false // 3. 二维数组map表示迷宫 // 4. i, j 就是老鼠的位置,初始化位置为(1, 1) // 5. 递归找路,规定map数组各个值的含义: // 0表示可以走,1表示障碍物,2表示已走路线,3表示走过但是走不通 // 6. 当map[6][5] = 2,说明找到通路就可以退出,否则继续找 // 7. 先确定老鼠找路的策略:下 -> 右 -> 上 -> 左(策略不唯一 ——> 找到的路径不唯一) public boolean findWay(int[][] map, int i, int j) { if (map[6][5] == 2) { // 说明已经找到,返回true return true; } else { if (map[i][j] == 0) { // 当前位置为0,表示可以走 // 假定当前位置可以走通 map[i][j] = 2; // 使用找路策略,来确定当前位置可以走通:下 -> 右 -> 上 -> 左 if (findWay(map, i + 1, j)) { // 先走下 return true; } else if (findWay(map, i, j + 1)) { // 右 return true; } else if (findWay(map, i - 1, j)) { // 上 return true; } else if (findWay(map, i, j - 1)) { // 左 return true; } else { map[i][j] = 3; // 表示当前位置走过但是走不通 return false; } } else { // map[i][j] = 1, 2, 3 return false; // 其他情况,返回false } } } }

汉诺塔问题

有三根柱子,在一个柱子上从下往上按照从大到小顺序摞着n个圆盘。现在要把这n个圆盘按照大小顺序重新摆放在另一根柱子上,并且规定在小圆盘上不能放大圆盘,且三根柱子之间一次只能移动一格圆盘。

public class HanoiTower { public static void main(String[] args) { Tower solve = new Tower(); solve.move(5, 'A', 'B', 'C'); } } class Tower { // move()方法表示移动圆盘的操作 // num: 要移动的圆盘个数 // a, b, c: 表示三根柱子A塔,B塔,C塔 public void move(int num, char a, char b, char c) { // 如果只有一个盘num = 1 if (num == 1) { System.out.println(a + "->" + c); } else { // 如果有多个盘,可以看成两块,最下面的一块盘和上面的所有盘(num - 1) // 1. 先移动上面所有的盘到b, 借助c move(num - 1, a, c, b); // 2. 把最下面的盘移动到c System.out.println(a + "->" + c); // 3. 再把b塔的所有盘移动到c,借助a move(num - 1, b, a, c); } } }

八皇后问题(回溯算法的典型)

在 $8×8$ 的国际象棋上摆放八个皇后,使其不能互相攻击。即:任意两个皇后都不能处于同一行、同一列或同一斜线上,共有多少种摆法?

思路分析:

第一个皇后先放在第一行第一列。

第二个皇后放在第二行第一列,然后判断是否可行,如果不可行,继续放在第二列、第三列,依次把所有列都放完直到找到一个合适的方案。

继续第三个皇后,以此类推,直到第八个皇后也能放在合理的位置,则找到一个正确解。

当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,即将第一个皇后,放在第一列的所有正确解,全部得到。

然后回头继续第一个皇后放第二列,继续循环1-4的步骤

注:理论上要创建二维数组表示棋盘,实际可以通过算法,用一个一维数组解决。arr[8] = {0, 4, 7, 5, 2, 6, 1, 3}对应arr下标表示第几行,即第几个皇后,arr[i] = val表示第i + 1个皇后,放在第i + 1行的第val + 1列。

6.5、方法重载

Java中允许同一个类中,多个同名方法的存在,但要求形参列表不一致,这种现象称为方法重载(overload)。

如:System.out.println(); out是PrintStream类型的对象。

方法重载注意:

方法名必须相同。 参数列表必须不同(1.形参类型不同 2.形参个数不同 3.形参顺序不同 ——> 至少一样不同),与参数名无关。 与方法返回类型无关。 当传入的实参与多个重载方法匹配时(出现自动类型转换),优先调用无自动类型转换的方法。 public class OverLoad { public static void main(String[] args) { MyCalculator mc = new MyCalculator(); System.out.println(mc.calculate(1, 2)); // 优先调用无需转换类型的方法calculate(int, int) System.out.println(mc.calculate(1, 3.14)); System.out.println(mc.calculate(5.2, 0)); } } class MyCalculator { // 下面四种不同calculate()方法构成了方法的重载 public int calculate(int n1, int n2) { return n1 + n2; } public double calculate(int n1, double n2) { return n1 + n2; } public double calculate(double n1, int n2) { return n1 + n2; } public int calculate(int n1, int n2, int n3) { return n1 + n2 + n3; } } 6.6、可变参数

Java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。可以通过可变参数(Variable parameters)实现。

基本语法:

访问修饰符 返回类型 方法名(数据类型...形参名) { 方法体; }

案例:类Method,方法sum(可以计算 2个数的和,3个数的和,4.5。。。)。

public class VarParameter { public static void main(String[] args) { Method method = new Method(); int res = method.sum("variable parameter", 1, 2, 3, 4, 5, 6); System.out.println(res); int[] arr = {985, 211, 520}; int result = method.sum("传递数组", arr); System.out.println("传递arr数组求和 = " + result); System.out.println(method.calScore("Mi Lan", 99.0, 92, 87, 95.5, 90)); } } class Method { // 可以使用方法重载实现 /* public int sum(int n1, int n2) { return n1 + n2; } public int sum(int n1, int n2, int n3) { return n1 + n2 + n3; } public int sum(int n1, int n2, int n3, int n4) { return n1 + n2 + n3 + n4; } */ // ...... // 以上三个方法名相同,功能相同,形参个数不同 -> 使用可变参数优化 // int...: 表示接收的是可变参数,类型是int,即可以接收多个int(0——>n),本质上是个数组 // 遍历nums求和 public int sum(String str, int... nums) { System.out.println("接收的参数个数 = " + nums.length); int res = 0; for (int i = 0; i < nums.length; i++) { res += nums[i]; } return res; } public String calScore(String name, double... scores) { double totalScore = 0; for (int i = 0; i < scores.length; i++) { totalScore += scores[i]; } return name + "有" + scores.length + "门课的成绩总分为" + totalScore; } }

注意:

可变参数的实参可以为0个或任意多个。 可变参数的实参可以为数组。 可变参数的本质就是可变长度的数组。 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在形参列表最后。 一个形参列表中只能出现一个可变参数。 6.7、作用域

Java中主要变量就是属性(成员变量)和局部变量。

局部变量一般是指在成员方法中定义的变量。

Java中作用域的分类:

全局变量:也就是属性,作用域为整个整体。可以被本类使用,或被其它类通过对象调用。 局部变量:也就是除了属性之外的其他变量,作用域为从定义它的语句开始到代码块执行结束。只能在本类中对应的方法中使用。

全局变量可以不赋值,直接使用,因为有默认值。局部基本数据类型变量(没有默认值)必须赋值后才能使用。

注意:

属性和局部变量可以重名,访问时遵循就近原则(在方法中与属性同名的局部变量会暂时覆盖属性,要访问属性需要借助this或者对象名)。

在同一个作用域中,变量不能重名。如在同一个成员方法中,两个局部变量不能重名。

属性生命周期较长,伴随着对象的创建而创建,伴随着对象的死亡而死亡。局部变量生命周期较短,伴随着它的定义语句执行而创建,伴随着代码块的结束而死亡。

全局变量可以加修饰符,局部变量不可以加修饰符

public class VarScope { String name; // 默认值为null public int age; // 属性可以加修饰符 public static void main(String[] args) { VarScope varscope = new VarScope(); varscope.hi(); } // 对象varscope伴随着程序终止而被销毁 public void hi() { int num; // 此时num没有值,直接输出会报未初始化赋值的错误 // int num; // 同一个作用域中,变量不能重名 num = 10; System.out.println(num); String name = "Mi Lan"; // 与属性同名,在这个方法体内,局部变量name覆盖属性name System.out.println(name); System.out.println(this.name); // 通过this关键字可以访问该类属性name int res = 0; // 局部变量不能加修饰符 for (int i = 0; i < 100; i++) { // 局部变量 i 的作用域伴随着for循环结束而终止。 res += i; }// 此处退出循环后,局部变量i立即销毁 } } 6.8、构造器

构造器(构造方法, constructor)是类的一种特殊方法,它的主要作用是完成对新对象的初始化。在创建对象时,系统会自动的调用该类的构造器完成对该对象的初始化。

基本语法:

[修饰符] 方法名(形参列表) { 方法体; }

说明:

构造器的修饰符可以默认,也可以为public、private、protected。 构造器没有返回值类型,也不能写void。 构造方法的方法名和类名必须一样。 参数列表和其他成员方法一样的规则。 构造器的调用由系统完成。 如果不定义构造器,系统会给类创建一个默认无参构造器。一旦定义了有参构造器,就会覆盖默认无参构造器,无法再使用无参构造,除非再显式定义一个无参构造器。 一个类可以定义多个不同的构造器,即构造器重载。 构造器的任务是完成对象属性的初始化,而不是创建对象。 public class Constructor { public static void main(String[] args) { Person p1 = new Person("Mi Lan", 22); System.out.println("p1对象name: " + p1.name + ", 芳龄: " + p1.age); } } class Person { String name; int age; // 构造器 public Person(String pName, int pAge) { name = pName; age = pAge; } } class Dog { String name; int age; /*默认构造器,如果未定义有参构造器,则系统自动创建默认构造器。可以使用javap反编译显示出来 Dog() { } */ Dog(String dogName, int dogAge) { // 有参构造器,会覆盖掉默认构造器(此时无法调用无参构造器) } // 显式定义无参构造器 Dog() { } }

反编译工具

使用javap指令能对给定的.class文件提供的字节码进行反编译成.java文件。

通过javap指令,可以对照源代码和字节码,从而了解很多编译器内部的工作,对更深入的理解如何提高程序执行的效率等问题有很大帮助。

基本语法:

javap

常用:javap -c -v 类名

javap常用指令 含义 -help --help -? 输出此用法消息 -version 版本消息 -v -verbose 输出附加信息 -l 输出行号和本地变量表 -public 仅显示公共类和成员 -protected 显示受保护的/公共类和成员 -package 显示程序包/受保护的/公共类和成员(默认) -p -private 显示所有类和成员 -c 对代码进行反汇编 -s 输出内部类型签名 -sysinfo 显示正在处理的类的系统信息(路径, 大小, 日期, MD5 散列) -constants 显示最终常量 -classpath 指定查找用户类文件的位置 -cp 指定查找用户类文件的位置 -bootclasspath 覆盖引导类文件的位置

创建对象的流程分析

案例代码

class Person { int age = 18; String name; Person(String pName, int pAge) { // 构造器 name = pName; // 给属性赋值 age = pAge; } public static void main(String[] args) { Person p = new Person("Mi Lan", 22); } }

流程分析(面试题)

加载Person类信息(Person.class),只会加载一次。 使用new给对象在堆中分配空间。 完成对象初始化 默认初始化。给对象的属性根据数据类型赋默认值。如:name = null,age = 0; 显式初始化。类属性是否初始化。如上面代码中int age = 18;。 构造器的初始化。将构造器的实参赋给对象属性。如上面代码中new Person("Mi Lan", 22); 将对象在堆中的地址返回给对象引用(对象名)。如上面代码中Person p = new Person("Mi Lan", 22); 6.9、this

Java虚拟机(JVM)会给每个对象分配this,代表当前对象。可以理解成,当new给对象在堆中分配空间时,会自动给该对象分配一个“隐藏的属性”——this,这个this类似C语言中的指针,只是这个“指针”永远指向包含该“指针”的对象。

简单来说:哪个对象调用,this就代表哪个对象。

public class UseThis { public static void main(String[] args) { Dog dog1 = new Dog("大壮", 3); System.out.println("dog1的hashCode = " + dog1.hashCode());// dog1的hashCode和构造器内部打印this的hashCode一致 dog1.printDogInfo(); // 打印的hashCode和dog1的hashCode一致 Dog dog2 = new Dog("大黄", 2); System.out.println("dog2的hashCode = " + dog2.hashCode());// dog2的hashCode和构造器内部打印this的hashCode一致 dog1.printDogInfo(); // 打印的hashCode和dog2的hashCode一致 // 结论:this指代当前对象 } } class Dog { public String name; public int age; public Dog(String name, int age) { // this.name 就是当前对象的属性name this.name = name; // this.age 就是当前对象的属性age this.age = age; // hashCode():返回该对象的哈希码值 // hashCode会根据在内存中不同对象的地址映射成不同的整数 System.out.println("this.hashCode = " + this.hashCode()); } public void printDogInfo() {// 打印狗的信息 System.out.println(name + "\t" + age + "\t"); System.out.println("Info方法中this.hashCode = " + this.hashCode()); } }

this关键字的本质(⭐⭐⭐⭐)

注意:

this关键字可以用来访问本类的属性、方法、构造器。 this用于区分当前类的属性和局部变量。 访问成员方法的语法:this.方法名(参数列表); 访问构造器的语法:this(参数列表);注意只能在构造器中使用,即只能在构造器中使用this调用另外一个构造器。 this不能在类定义的外部使用,只能在类定义的方法中使用。 如果要在构造器中使用this访问另外一个构造器,则 this(参数列表); 这个语句必须是构造器中的第一个语句。 class ThisDetail { public static void main(String[] args) { T t1 = new T("张三", 30); t1.f2(); T t2 = new T(); t2.f3(); } } class T { public String name = "smith"; public int age = 18; public T() {// 默认构造器 // 在默认构造器中访问有参构造器 this("Mi Cai", 23); // 使用this访问另一个构造器,必须是构造器中的第一个语句 System.out.println("T()构造器"); } public T(String name, int age) {// 有参构造器 this.name = name; this.age = age; System.out.println("T(String name, int age)构造器"); } public void f1() { System.out.println("f1()方法..."); } public void f2() { System.out.println("f2()方法..."); // 调用本类的f1方法 // 第一种方式 f1(); // 第二种方式,与第一种方式有区别(继承介绍) this.f1(); } public void f3() { String name = "Mi Lan"; // 就近原则,name指代局部变量name,age指代属性age System.out.println("name = " + name + ", age = " + age); // Mi Lan, 18 // 使用this访问当前对象的属性 System.out.println("name = " + this.name + ", age = " + this.age); // smith, 18 } } 6.10、实战运用

人机猜拳游戏

import java.util.Random; import java.util.Scanner; /* 人机猜拳游戏 电脑每次随机生成0, 1, 2 0 表示石头 1表示剪刀 2表示布 并统计人的输赢次数 */ public class MoraGame { public static void main(String[] args) { System.out.println("猜拳游戏\n0 表示石头 1表示剪刀 2表示布"); // 创建一个玩家对象 Player player = new Player(); // 声明一个变量统计输赢的次数 int isWinCount = 0; // 创建一个二维数组,接收局数,玩家出拳以及电脑出拳的情况 int[][] arr1 = new int[3][3]; int j = 0; // 创建一个一维数组,用来接收输赢的情况 String[] arr2 = new String[3]; Scanner scanner = new Scanner(System.in); for (int i = 0; i < 3; i++) { // 获取玩家出拳 System.out.println("请输入你要出的拳(0-石头, 1-剪刀, 2-布):"); int num = scanner.nextInt(); player.setPlayerGuessNum(num); int playerGuess = player.getPlayerGuessNum(); arr1[i][j + 1] = playerGuess; // 获取电脑出拳 int computerGuess = player.setComputerGuessNum(); arr1[i][j + 2] = computerGuess; // 将玩家猜的拳和电脑比较 String isWin = player.vsComputer(); arr2[i] = isWin; arr1[i][j] = player.count; // 对每一局的情况输出 System.out.println("====================================="); System.out.println("局数\t玩家的出拳\t电脑的出拳\t输赢情况"); System.out.println(player.count + "\t\t" + playerGuess + "\t\t\t" + computerGuess + "\t\t\t" + isWin); System.out.println("====================================="); System.out.println("\n"); isWinCount = player.winCount(isWin); } // 对游戏的最终结果进行输出 System.out.println("局数\t玩家的出拳\t电脑的出拳\t\t输赢情况"); int a; for (a = 0; a < arr1.length; a++) { for (int b = 0; b < arr1[a].length; b++) { System.out.print(arr1[a][b] + "\t\t\t"); } System.out.print(arr2[a]); System.out.println(); } System.out.println("你赢了" + isWinCount + "次"); } } class Player { // 玩家出拳的类型 int playerGuessNum; // 电脑出拳的类型 int computerGuessNum; // 玩家赢的次数 int winCount; // 比赛的总场数 int count = 1; /** * 设置电脑随机生成猜拳的数字的方法 * * @return :返回电脑猜拳的数字 */ public int setComputerGuessNum() { Random r = new Random(); this.computerGuessNum = r.nextInt(3); return computerGuessNum; } /** * 获取玩家猜拳的数字的方法 * * @return :返回玩家猜拳的数字 */ public int getPlayerGuessNum() { return playerGuessNum; } /** * 设置玩家猜拳的方法 * * @param playerGuessNum :玩家猜拳的数字 */ public void setPlayerGuessNum(int playerGuessNum) { if (playerGuessNum > 2 || playerGuessNum < 0) { throw new IllegalArgumentException("数字输入错误"); } this.playerGuessNum = playerGuessNum; } /** * 统计胜场次数 * * @param s :胜负情况 * @return :返回胜场次数 */ public int winCount(String s) { count++; // 总场数自增 if (s.equals("你赢了")) { winCount++; } return winCount; } /** * 胜负判断 * * @return :返回胜负情况 */ public String vsComputer() { if (playerGuessNum == 0 && computerGuessNum == 1) {// 人:石头,机:剪刀 return "你赢了"; } else if (playerGuessNum == 1 && computerGuessNum == 2) {// 人:剪刀,机:布 return "你赢了"; } else if (playerGuessNum == 2 && computerGuessNum == 0) {// 人:布,机:石头 return "你赢了"; } else if (playerGuessNum == computerGuessNum) {// 一样 return "打平手"; } else { return "你输了"; } } }

IDEA

IDEA介绍

IDEA全称IntelliJ IDEA。

在业界被公认为最好的Java开发工具。

IDEA是JetBrains公司的产品。

除了支持Java开发,还支持HTML,CSS,PHP,MySQL,Python等。

安装:官网

IDEA界面

上边——菜单区,左边——项目导航区,右边——代码编辑区,下边——输出的控制台区

设置字体:

外观和行为Appearance & Behavior -> Appearance 文件file -> settings -> Editor -> Font

在IDEA中,使用run一个.java文件时,会把该文件编译成.class字节码文件,然后再运行。

编译生成的.class文件会自动在项目目录下的out文件夹

IDEA常用快捷键

修改快捷键:文件file -> settings -> Keymap -> Editor Actions

删除当前行:Ctrl + D 快速格式化代码:Ctrl + Shift + F 快速运行程序:Alt + R。如果需要使用快捷键运行,需要先配置主类。第一次运行需要鼠标右键Run。 生成构造器等:Alt + Insert 查看一个类的层级关系:Ctrl + H 将光标放在一个方法上,定位到哪个类的方法:Ctrl + B

模板:文件file -> settings -> Editor -> Live templates

7、面向对象编程(中级) 7.1、包

作用:

区分很多相同名字的类。 当类很多时,可以很好的管理类。 控制访问范围。

基本语法:

package 包名;

package关键字,表示打包。

包的本质分析(原理):实际上就是创建不同的文件夹来保存类文件

包的命名规则:只能包含数字、字母、下划线、小圆点.,但不能用数字开头,不能是关键字或保留字

包的命名规范:一般是小写字母+小圆点.。一般是com.公司名firm.项目名project.业务模块名model。

com.sina.crm.user// 用户模块

com.sina.crm.order// 订单模块

com.sina.crm.utils// 工具类

Java中常用的包(.*表示所有包):

java.lang.*:lang包是基本包,默认引入,不需要再引入 java.util.*:util包,系统提供的工具包,工具类 java.net.*:网络包,网络开发 java.awt.*:Java图形化界面开发(GUI)的包

注意:

package 包名;的声明必须放在类的最上面,一个类中最多一句package。 import指令位置放在package下面,在类定义前面。 7.2、访问修饰符

Java提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):

公开级别:用public修饰,对整个项目所有包中的类公开。 受保护级别:用protected修饰,对子类和同一个包中的类公开。 默认级别:没有修饰符号,只向同一个包中的类公开。 私有级别:用private修饰,只有类本身可以访问,不对外公开。

访问修饰符的权限图(⭐⭐⭐⭐⭐)

注意:

修饰符可以用来修饰类中的属性,成员方法以及类。 只有默认的和public才能修饰类,并且遵循以上访问权限的特点。 成员方法的访问规则和属性完全一样。 默认级别的子类对象不能访问不同包下父类受保护级别的方法或属性。

com.yxz.modifier.A.java

package com.yxz.modifier; public class A { // 四个属性,分别使用不同的访问修饰符来修饰 public int n1 = 100; protected int n2 = 200; int n3 = 300; private int n4 = 400; public void m1() { // 同一个类中可以访问public、protected、默认、private修饰的属性或方法 System.out.println("n1 = " + n1 + ", n2 = " + n2 + ", n3 = " + n3 + ", n4 = " + n4); } protected void m2() { } void m3() { } private void m4() { } public void say() { // 同一个类中可以访问public、protected、默认、private修饰的属性或方法 m1(); m2(); m3(); m4(); } }

com.yxz.modifier.B.java

package com.yxz.modifier; public class B { public void hi() { A a = new A(); // 在同一个包下,可以访问 public,protected和默认修饰属性或方法,不能访问private System.out.println("n1 = " + a.n1 + ", n2 = " + a.n2 + ", n3 = " + a.n3); a.m1(); a.m2(); a.m3(); // a.m4(); 错误:不同类中无法访问private修饰的方法 } }

com.yxz.modifier.Test.java

package com.yxz.modifier; public class Test { public static void main(String[] args) { A a = new A(); a.m1(); B b = new B(); b.hi(); } } // 只有默认和public可以修饰类 class Tiger { }

com.yxz.pkg.Test.java

package com.yxz.pkg; import com.yxz.modifier.A; public class Test { public static void main(String[] args) { A a = new A(); // 在不同包下,可以访问public修饰的属性或方法 // 但是不能访问protected、默认、private修饰的属性或方法 System.out.println(a.n1); a.m1(); // 在不同包下,不能访问protected、默认、private修饰的属性或方法 // a.m2(); // a.m3(); // a.m4(); B b = new B(); // b.m2(); // 默认级别的子类对象不能访问不同包下父类受保护级别的方法 // System.out.println(b.n2); // 默认级别的子类对象不能访问不同包下父类受保护级别的属性 } } class B extends A { }

面向对象编程(OOP, Object-oriented programming)三大特征:封装、继承、多态。

7.3、封装

封装(encapsulation)就是把抽象出的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作(方法)才能对数据进行操作。

封装的好处:

隐藏实现的细节,调用者无需关注内部运行原理。 可以对数据进行验证,保证数据的安全。

封装的实现步骤:

将属性进行私有化private(外部不能直接修改属性)。

提供一个公共的(public)set方法,用于对属性判断并赋值。

public void setXxx(数据类型 参数名) {// Xxx表示某个属性 // 数据验证的业务逻辑 this.属性 = 参数名; }

提供一个公共的get方法,用于获取属性的值。

public 返回值类型 getXxx() {// Xxx表示某个属性 // 权限判断 return xxx; }

综合案例

编写一个类Person,属性(姓名,年龄,工作,薪水) 要求:外部不能随便查看人的年龄、工资等隐私 并对设置的年龄进行合理的验证,年龄合理就设置,否则给默认年龄。必须在1-120之间 工资不能直接查看,name的长度在2-6个字符。

package com.yxz.encap; public class Encapsulation { public static void main(String[] args) { Person person = new Person(); // person.name = "Mi cai"; 名字长度需要判断是否合理 // person.age = 22; 错误:不同类之间不能访问私有属性 person.setName("米彩"); person.setAge(23); person.setSalary(1000000.0); person.setJob("总裁"); System.out.println(person.toString()); Person person1 = new Person("米兰", 2000, 300000.0, "经理"); System.out.println(person1.toString()); } } class Person { public String name; // 名字公开 private int age; // 年龄私有化 private double salary; // 薪水 私有化 private String job; public Person() { } public Person(String name, int age, double salary, String job) { // this.name = name; // this.age = age; // this.salary = salary; // this.job = job; setName(name); setAge(age); setSalary(salary); setJob(job); } public String getName() { return name; } public void setName(String name) { // 加入对数据的校验,增加业务逻辑 if (name.length() >= 2 && name.length() = 1 && age 自动调用匹配的方法(方法重载) System.out.println(b.sum(1, 2)); System.out.println(b.sum(10, 20, 30)); } } class A {// 父类 public void say() { System.out.println("父类 A中的say()方法被调用"); } } class B extends A { public int sum(int n1, int n2) {// 和下面的sum方法构成方法重载 return n1 + n2; } public int sum(int n1, int n2, int n3) { return n1 + n2 + n3; } public void say() { // 和父类A中的say方法构成方法重写 System.out.println("子类 B中的say()方法被调用"); } }

对象的多态(核心)

编译类型 对象引用 = new 运行类型();

一个对象的编译类型和运行类型可以不一致。

编译类型在定义对象时就已经确定,不能改变。

对象的运行类型是可以变化的,可以通过getClass()方法获取对象的运行类型。

编译类型看定义时=号的左边,运行类型看=号的右边。

Animal animal = new Animal(); // animal的编译类型和运行类型一致,都是Animal Animal animal = new Dog(); // animal编译类型是Animal,运行类型Dog animal = new Cat(); // animal的运行类型变成了Cat,编译类型仍然是Animal // 结论:父类的引用(对象名)可以指向父类对象,也可以指向父类的子类对象 package com.yxz.poly; public class PolyObject { public static void main(String[] args) { Animal animal = new Animal(); // animal 编译类型:Animal,运行类型:Animal animal.say(); animal = new Cat(); // animal 编译类型:Animal,运行类型:Cat animal.say(); animal = new Dog(); // animal 编译类型:Animal,运行类型:Dog animal.say(); } } /**控制台输出: * Animal cry() 动物叫... * Cat cry() 小猫喵喵叫... * Dog cry() 小狗汪汪叫... */ class Animal { public void say() { System.out.println("Animal cry() 动物叫..."); } } class Cat extends Animal { public void say() { System.out.println("Cat cry() 小猫喵喵叫..."); } } class Dog extends Animal { public void say() { System.out.println("Dog cry() 小狗汪汪叫..."); } }

注意:多态的前提是:两个对象(类)存在继承关系

多态的向上转型

向上转型的本质:父类的引用指向了子类的对象。

语法:父类类型 对象引用 = new 子类类型();

向上转型调用方法的规则如下:

可以调用父类中的所有成员(遵守访问权限规则)。

不能调用子类中特有成员,无法通过编译。

原因:因为在编译阶段,能调用哪些成员,是由对象引用的编译类型来决定。

最终运行效果(运行阶段)看子类(对象引用的运行类型)的具体实现。即:在运行阶段调用方法时,根据对象引用的运行类型,从子类(运行类型)中开始查找方法(遵循访问成员的规则)。

多态的向下转型

语法:子类类型 引用名 = (子类类型)父类引用。 只能强转父类的引用,不能强转父类的对象。 要求父类的引用必须指向的是当前目标类型的对象。 当向下转型后,可以调用子类类型中的所有的成员(向下转型的目的)。

注意:

属性没有重写之说。属性的值看对象引用的编译类型。 instanceOf比较操作符:用于判断对象的运行类型是否为XX类型或XX类型的子类型。 package com.yxz.poly; public class PolyDetail { public static void main(String[] args) { // 父类引用指向子类对象(向上转型) // 语法:父类类型 引用名 = new 子类类型(); Animal animal = new Cat(); Object obj = new Cat(); // 可以调用父类中的所有成员(遵守访问权限) // 不能调用子类的特有成员 // animal.catchMouse(); 编译错误,无法通过编译 // 在编译阶段,父类引用能调用哪些成员是由编译类型决定 // 最终运行效果(运行阶段)看子类(运行类型)的具体实现 // 子类重写了eat方法,所以优先调用子类的eat方法。 animal.eat(); // 猫吃鱼 animal.sleep(); // 睡觉 animal.run(); // 奔跑 animal.show();// 你好 // 属性的值看编译类型 System.out.println("animal.age = " + animal.age); // anmal编译类型:Animal,输出10 // animal的编译类型:Animal,运行类型:Cat Cat cat = (Cat) animal; // 向下转型 cat.catchMouse(); System.out.println("cat.age = " + cat.age); // cat编译类型:Cat,输出20 System.out.println(cat instanceof Animal); // cat的运行类型:Cat,是Animal或其子类:true System.out.println(animal instanceof Cat); // animal的运行类型:Cat,是Cat或其子类:true Object obj1 = new Object(); System.out.println(obj1 instanceof Animal); // obj1的运行类型:Object,是Animal或其子类:false } } /** * 控制台输出: * 猫吃鱼 * 睡觉 * 奔跑 * 你好 * animal.age = 10 * 猫抓老鼠 * cat.age = 20 * true * true * false */ class Animal { String name = "动物"; int age = 10; public void sleep() { System.out.println("睡觉"); } public void run() { System.out.println("奔跑"); } public void eat() { System.out.println("吃饭"); } public void show() { System.out.println("你好"); } } class Cat extends Animal { int age = 20; public void eat() { System.out.println("猫吃鱼"); } public void catchMouse() {// Cat特有方法 System.out.println("猫抓老鼠"); } }

Java的动态绑定机制(非常非常非常重要)

当调用对象方法的时候,该方法会和该对象的内存地址(运行类型)进行绑定。 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。 package com.yxz.poly; public class DynamicBinding { public static void main(String[] args) { A a = new B();// 向上转型 // 1. a调用方法sum(),先从运行类型(B类)开始查找 // 2. 运行类型中没有sum方法 —> 根据继承机制,到父类中查找sum方法 // 3. 父类中有sum方法,调用 // 4. 父类sum方法中调用了getI方法(子类父类都有),根据动态绑定机制 —> 该方法和该对象内存地址(运行类型)绑定 // 5. 再到运行类型(B类)中查找getI方法,在B类中找到getI方法 // 6. getI方法访问属性i,根据属性没有动态绑定机制,哪里声明哪里使用,先找子类中的i = 20 // 7. 返回到父类中的sum方法,返回i + 10 = 30 System.out.println(a.sum()); // 30 System.out.println(a.sum1()); // 20 } } class A { public int i = 10; public int sum() { return getI() + 10; } public int sum1() { return i + 10; } public int getI() {// 父类getI return i; } } class B extends A { public int i = 20; // public int sum() { // return getI() + 20; // } // public int sum1() { // return i + 10; // } public int getI() {// 子类getI return i; } }

多态的应用

多态数组

数组的定义类型为父类类型,里面保存的实际元素类型为子类类型。

应用实例:创建一个Person对象、2个Student对象和2个Teacher对象,统一放在数组中,并调用每个对象的say方法。

Person.java文件

package com.yxz.poly.polyarr; public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String say() { return name + "\t" + age; } }

Student.java文件

package com.yxz.poly.polyarr; public class Student extends Person { private double score; public Student(String name, int age, double score) { super(name, age); this.score = score; } public double getScore() { return score; } public void setScore(double score) { this.score = score; } @Override public String say() { return "学生 " + super.say() + " \t\tscore = " + score; } // 特有方法 public void study() { System.out.println("学生" + getName() + "正在学java..."); } }

Teacher.java文件

package com.yxz.poly.polyarr; public class Teacher extends Person { private double salary; public Teacher(String name, int age, double salary) { super(name, age); this.salary = salary; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } @Override public String say() { return "老师 " + super.say() + " \t\tsalary = " + salary; } // 特有方法 public void teach() { System.out.println("老师" + getName() + "正在授课..."); } }

PloyArray.java文件

package com.yxz.poly.polyarr; public class PloyArray { public static void main(String[] args) { Person[] persons = new Person[5]; persons[0] = new Person("jack", 20); persons[1] = new Student("mary", 18, 100); persons[2] = new Student("smith", 19, 30.1); persons[3] = new Teacher("milan", 30, 20000); persons[4] = new Teacher("king", 29, 25000); // 循环遍历多态数组,调用say for (int i = 0; i < persons.length; i++) { // persons[i] 编译类型:Person,运行类型是根据实际情况由JVM判断 System.out.println(persons[i].say()); // 动态绑定机制 if (persons[i] instanceof Student) { // 判断运行类型是否是Student类 ((Student) persons[i]).study(); //向下转型 } else if (persons[i] instanceof Teacher) { // 判断运行类型是否是Teacher类 ((Teacher) persons[i]).teach(); //向下转型 } else if (persons[i] instanceof Person) { // 判断运行类型是否是Person类 // 不做任何处理 } else { System.out.println("类型有误..."); } } } }

多态参数

方法定义的形参类型为父类类型,实参类型允许为子类类型。

案例1:主人喂动物食物。

定义一个动物类Animal,属性name(private)。狗类Dog和猫类Cat继承动物类。 定义一个食物类Food,属性name(private)。骨头类Bone和鱼类Fish继承食物类。 定义一个主人类Master,属性name(private)。添加一个喂食feed(Animal animal, Food food)方法,根据传入的不同类型分别打印不同的信息。 测试类中调用方法feed,分别传入不同对象。

Animal.java文件

package com.yxz.poly.method; public class Animal {// 动物类 private String name; public Animal(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } class Dog extends Animal { public Dog(String name) { super(name); } } class Cat extends Animal { public Cat(String name) { super(name); } }

Food.java文件

package com.yxz.poly.method; public class Food {// 食物类 private String name; public Food(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } class Fish extends Food { public Fish(String name) { super(name); } } class Bone extends Food { public Bone(String name) { super(name); } }

Master.java文件

package com.yxz.poly.method; public class Master {// 主人类 private String name; public Master(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void feed(Animal animal, Food food) { // 参数多态 System.out.println("主人" + getName() + "给宠物 " + animal.getName() + " 喂 " + food.getName() + " 吃"); } }

Polymorphic.java——测试类

package com.yxz.poly.method; public class Polymorphic { public static void main(String[] args) { // 创建主人对象 Master tom = new Master("tom"); // 创建小狗 Dog dog = new Dog("大黄"); Bone bone = new Bone("大棒骨"); tom.feed(dog, bone); // 创建小猫 Cat cat = new Cat("花猫"); Fish fish = new Fish("黄花鱼"); tom.feed(cat, fish); } }

案例2:计算不同员工的年薪

定义员工类Employee,属性包含姓名和月工资(private),以及计算年工资getAnnual方法。 普通员工Worker和经理Manager继承了员工。经理类多了奖金bonus属性和管理manage方法,普通员工类多了work方法,普通员工和经理类要求分别重写getAnnual方法。 测试类中添加一个方法showEmployeeAnnual(Employee e),实现获取任何员工对象的年工资,并在main方法中调用该方法e.getAnnual。 测试类中添加一个方法testWork(Employee e),如果是普通员工,则调用work方法,如果是经理,则调用manage方法。

Employee.java文件

package com.yxz.ployparameter; public class Employee { private String name; private double salary; public Employee(String name, double salary) { this.name = name; this.salary = salary; } /** * 获取年工资的方法 * * @return :年工资 */ public double getAnnual() { return 12 * salary; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } }

Worker.java文件

package com.yxz.ployparameter; public class Worker extends Employee { public Worker(String name, double salary) { super(name, salary); } @Override public double getAnnual() { return super.getAnnual(); } public void work() { System.out.println("普通员工 " + getName() + " is working..."); } }

Manager.java文件

package com.yxz.ployparameter; public class Manager extends Employee { private double bonus; public Manager(String name, double salary, double bonus) { super(name, salary); this.bonus = bonus; } public double getBonus() { return bonus; } public void setBonus(double bonus) { this.bonus = bonus; } @Override public double getAnnual() { return super.getAnnual() + bonus; } public void manage() { System.out.println("经理" + getName() + " is managing..."); } }

PloyParameter.java——测试类

package com.yxz.ployparameter; public class PloyParameter { public static void main(String[] args) { Worker tom = new Worker("Tom", 3000); Manager miCai = new Manager("MiCai", 13000, 200000); PloyParameter ployParameter = new PloyParameter(); ployParameter.showEmployeeAnnual(tom); ployParameter.testWork(tom); ployParameter.showEmployeeAnnual(miCai); ployParameter.testWork(miCai); } public void showEmployeeAnnual(Employee e) { System.out.println(e.getAnnual()); // 动态绑定机制 } // testWork方法:如果是普通员工,调用work方法,如果是经理,调用manage方法 public void testWork(Employee e) { if (e instanceof Worker) { ((Worker) e).work(); // 向下转型 } else if (e instanceof Manager) { ((Manager) e).manage(); // 向下转型 } else { // 不做处理 } } } 7.6、super

super代表父类的引用,用于访问父类的属性、方法、构造器。

基本语法:

super.属性名; 访问父类的属性,但不能访问父类的private属性。 super.方法名(参数列表); 访问父类的方法,但不能访问父类的private方法。 super(参数列表); 访问父类的构造器,只能在构造器的第一个语句。

super的作用:

调用父类的构造器 -> 分工明确,父类属性由父类初始化,子类的属性由子类初始化。

当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。如果没有重名,使用super、this、直接访问是一样的效果。

使用直接访问、this访问属性遵从就近原则,先在本类中找属性或方法,找不到再找父类。super会直接跳过本类直接到父类中找属性或方法。

super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以用super去访问爷爷类的成员。如果多个基类(超类)都有同名成员,使用super访问遵循就近原则

super和this的比较

区别点 this super 访问属性 优先访问本类中的属性,如果本类中没有此属性则从父类中继续查找 直接从父类开始访问属性 调用方法 优先访问本类中的方法,如果本类中没有此方法则从父类中继续查找 直接从父类开始访问方法 调用构造器 调用本类构造器,必须放在构造器的首行 调用父类构造器,必须放在子类构造器的首行 特殊 表示当前对象 子类中访问父类对象 7.7、overwrite

方法覆盖(方法重写, overwrite, override):子类有一个方法和父类的某个方法的名称、返回类型、参数一样,那么子类的这个方法就覆盖了父类的方法。不局限于直接父类,间接父类也可以触发方法覆盖。

构成方法覆盖的条件:

子类的方法的方法名、参数列表要和父类的方法的方法名、参数列表完全一样才能构成方法覆盖。 子类方法的返回值类型和父类方法的返回值类型一样,或者是父类返回值类型的子类。如:父类方法返回值类型Object类,子类方法返回值类型是String,其他一样,则构成方法覆盖。 子类方法不能缩小(可以放大或相等)父类方法的访问权限。public > protected > 默认 > private public class A { public void hi() { } protected Object say() { return null; } } class B extends A { public void hi() {// 构成方法覆盖 } public void hi(String name) {// 构成方法重载,不构成方法覆盖(形参列表不一致) } private void hi(int age) {// 构成方法重载,不构成方法覆盖(缩小了父类方法的访问范围) } public Object say() {// 构成方法覆盖(扩大了父类方法访问范围) return null; } // protected String say() {// 不构成方法重载,可以构成方法覆盖(返回类型String是父类方法返回类型Object类的子类) // return null; // } }

方法重载(overload)和方法重写(override)的区别

名称 发生范围 方法名 形参列表 返回值类型 访问修饰符 重载(overload) 本类 必须一致 参数类型、参数个数、参数顺序至少一个不同 无要求 无要求 重写(override) 父子类 必须一致 必须一致 子类重写的方法,返回值类型和父类返回值类型一致或者是其子类 子类方法的访问修饰符不能缩小父类方法的访问范围

案例(综合练习)

编写一个Person类,包括name, age属性(private),构造器、方法say(返回自我介绍的字符串)。 编写一个Student类,继承Person类,增加id、socre属性(private),以及构造器,定义say方法(返回自我介绍的字符串)。 在main()方法中,分别创建Person和Student对象,调用say方法输出自我介绍。 public class Test { public static void main(String[] args) { Person person = new Person("jack", 34); System.out.println(person.say()); Student student = new Student("杨明", 21, 123, 98.5); System.out.println(student.say()); } } class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String say() { return "name = " + name + " age = " + age; } } class Student extends Person { private int id; private double score; public Student(String name, int age, int id, double score) { super(name, age); this.id = id; this.score = score; } public int getId() { return id; } public void setId(int id) { this.id = id; } public double getScore() { return score; } public void setScore(double score) { this.score = score; } public String say() { return super.say() + " id = " + id + " score = " + score; } } 7.8、Object类详解

equals()方法

==和equals的对比

==:既可以判断基本类型,又可以判断引用类型。

如果判断基本类型,判断的是值是否相等。如:int i = 10; double d = 10.0;

如果判断引用类型,判断的是对象地址是否相等,即判定是不是同一个对象。

equals():是Object类中的方法,只能判断引用类型。默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。

如:Integer类和String类的equals()方法已经重写,不再判断地址,只判断内容。

Object类的equals()方法源码:

public boolean equals(Object obj) { return (this == obj); // 判断对象地址是否相等 }

查看JDK中Integer的equals()方法源码和String类的equals()方法源码:

// Integer类的equals()方法 public boolean equals(Object obj) {// 重写Object类的equals()方法 if (obj instanceof Integer) { // 判断对象是否属于Integer类 return value == ((Integer)obj).intValue(); // 向下转型并调用Integer的intValue()方法 } return false; } // String类的equals()方法 public boolean equals(Object anObject) {// 重写Object类的equals()方法 if (this == anObject) { // 如果是同一个对象则返回真 return true; } if (anObject instanceof String) { // 判断对象的运行类型是否属于String类 String aString = (String)anObject; // 向下转型,子类类型 对象引用 = (子类类型)父类引用 if (!COMPACT_STRINGS || this.coder == aString.coder) { return StringLatin1.equals(value, aString.value); } } return false; // 如果对象不属于String类,返回假 }

实例:创建一个Person类,属性包括name、age、gender(private),

判断两个Person对象的内容是否相等,如果两个Person对象的各个属性都一样,则返回true,反之false。

package com.yxz.object_; public class EqualsExercise01 { public static void main(String[] args) { Person person1 = new Person("jack", 10, '男'); Person person2 = new Person("jack", 10, '男'); System.out.println(person1.equals(person2)); // true System.out.println(person1 == person2); // false ==对引用类型判断是否是同一个对象地址 } } class Person { private String name;// 姓名 private int age;// 年龄 private char gender;// 性别 public Person(String name, int age, char gender) { this.name = name; this.age = age; this.gender = gender; } @Override public boolean equals(Object obj) { // 如果是同一个对象,返回true if (this == obj) { return true; } // 如果obj是Person对象 if (obj instanceof Person) { // 将obj对象向下转型,目的:需要得到子类的所有属性和方法 Person p = (Person) obj; return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender; } // 如果不是Person对象 return false; } }

finalize()方法 说明:

当对象被回收时,系统自动调用该对象的finalize()方法。子类可以重写该方法,做一些释放资源的操作。 什么时候被回收:当某个对象没有任何引用时,则JVM就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用finalize()方法。 垃圾回收机制的调用,是由系统决定,也可以通过System.gc()主动触发垃圾回收机制。 public class Finalize{ public static void main(String[] args) { Car bmw = new Car("宝马"); bmw = null; // 此时bmw指向的对象就是一个垃圾,垃圾回收器就会根据GC算法回收(销毁)对象,在销毁对象前,会调用该对象finalize()方法 } } class Car { private String name; public Car(String name) { this.name = name; } }

hashCode()方法

返回该对象的哈希码值(一般是通过将该对象的内部地址转换成一个整数来实现的),Object类支持此方法是为了提高哈希表的性能。

结论:

提高具有哈希结构的容器的效率。 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的。 两个引用,如果指向的是不同对象,则哈希值肯定不一样。 哈希值主要根据地址号来的,不能完全将哈希值等价于地址。 在集合等容器中如果需要hashCode,也会重写此方法。

toString()方法

该方法默认返回:全类名[email protected]+哈希值的十六进制。(全类名 = 包名 + 类名) 子类往往重写toString方法,用于返回对象的属性信息。在IDEA中使用快捷键Alt + Insert可以选择根据具体实现类的属性重写Object的toString()方法。

重写toString()方法,打印对象或者拼接对象时,都会自动调用该对象的toString形式。

当直接输出一个对象时,toString()方法会被默认调用。

Object类的toString()方法源码:

public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }

说明:

getClass():返回class+包名+类名. 如:class java.lang.String getClass().getName():获取全类名(包名+类名)。如:java.lang.String getClass().getSimpleName():获取类名. 如:String Integer.toHexString(hashCode()):将对象的hashCode值转成十六进制字符串。

案例:定义一个Monster类,属性包括name、job、salary。将Monster对象的属性输出

package com.yxz.object_; public class ToStringExercise { public static void main(String[] args) { Monster monster = new Monster("小妖怪", "巡山的", 1000); System.out.println(monster.toString()); System.out.println(monster); // 默认调用toString()方法 } } class Monster { private String name; private String job; private double salary; public Monster(String name, String job, double salary) { this.name = name; this.job = job; this.salary = salary; } @Override public String toString() { return "Monster{" + "name='" + name + '\'' + ", job='" + job + '\'' + ", salary=" + salary + '}'; } } 7.9、断点调试

在开发中,程序员查找错误时,可以用断点调试(breakpoint debug),一步一步的看源码执行的过程,从而发现错误所在。

重要提示:在断点调试过程中,是运行状态,是以对象的运行类型来执行的。

介绍:

断点调试是指在程序的某一行设置一个断点,调试时,程序运行到这一行就会停住,然后可以一步一步往下跳是,调试过程中可以看各个变量当前的值。出错的话,调试到出错的代码行即显示错误,停下。进行分析找到bug。 断点调试是程序员必须掌握的技能。 断点调试也能帮助我们查看Java底层源码的执行过程,提高Java水平。 断点调试快捷键: F7:跳入 -> 跳入方法体内执行。 F8:跳过 -> 逐行执行代码。 Shift + F8:跳出 -> 跳出方法。 F9:resume,执行到下一个断点。

8、项目实战运用 8.1、零钱通

项目需求:

使用Java开发零钱通项目,可以完成查看明细(功能1),收益入账(功能2),消费(功能3),退出系统(功能4)等功能。

在用户输入4时,给出提示“确定要退出吗?y/n”,必须输入正确的y/n,否则循环输入指令,直到输入y/n。

在收益入账和消费时,判断金额是否合理,并给出相应提示。

项目流程(化繁为简)

先完成菜单显示功能,并可以选择功能。

完成零钱通明细功能。方法:1、把收益入账和消费保存到数组 2、使用对象 3、使用String拼接。

完成收益入账功能。

完成消费功能。

退出系统功能。

在用户输入4时,给出提示“确定要退出吗?y/n”,必须输入正确的y/n,否则循环输入指令,直到输入y/n。

思路分析:

(1)定义一个变量choice,接收用户输入 (2)使用while + break,来处理接收到的输入是 y 还是 n (3)退出while后,再判断choice是y还是n,就可以决定是否退出 (4)建议一段代码,完成一个小功能,降低耦合度。

在收益入账和消费时,判断金额是否合理,并给出相应提示。

如何校验数据?过关斩将思想:排除所有非法数据即可 -> 先判断是否是非法数据,再执行正确数据的代码(提高代码可读性)

使用面向过程完成项目的功能后,使用OOP(Object-oriented Programming)思想封装(变量 -> 类属性,功能 -> 类方法)

项目源码

SmallChangeSysOOP.java文件

package com.yxz.smallchange.oop; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Scanner; /** * 该类是完成零钱通的各个功能的类 * 使用OOP(Object-Oriented Programming),面向对象编程 * 各个功能对应一个方法 */ public class SmallChangeSysOOP { // 类属性 // 1. 完成菜单显示功能,定义相关变量 boolean loop = true; Scanner scanner = new Scanner(System.in); String key = ""; // 2. 完成零钱通明细 String details = "------------------零钱通明细--------------"; // 3. 完成收益入账 double money = 0; // 入账金额 double balance = 0; // 余额 Date date = null; // date 是 java.util.Date类型,表示日期 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); // 用于日期格式化的对象 // 4. 完成消费 // 定义新变量,记录消费原因 String note = ""; public void mainMenu() { do { System.out.println("\n==================零钱通菜单=============="); System.out.println("\t\t\t\t1 零钱通明细"); System.out.println("\t\t\t\t2 收益 入账"); System.out.println("\t\t\t\t3 消费 支出"); System.out.println("\t\t\t\t4 退出 系统"); System.out.print("请选择(1-4): "); key = scanner.next(); switch (key) { case "1": this.detail(); break; case "2": this.income(); break; case "3": this.pay(); break; case "4": this.exit(); break; default: System.out.println("选择有误,请重新选择..."); } } while (loop); System.out.println("---------------已经退出零钱通--------------"); } // 完成零钱通明细 public void detail() { System.out.println(details); } // 收益入账 public void income() { System.out.print("收益入账金额:"); money = scanner.nextDouble(); // money的范围需要校验 if (money 系统有哪些类(文件),明确类与类的调用关系

每个层次完成不同功能 -> 各司其职

HouseVies.java类[View层,界面层]

显示界面 接收用户的收入 调用HouseService完成对房屋信息的各种操作

代码实现分析:

编写mainMenu()方法,界面显示主菜单。 编写listHouses()方法,调用业务层的list()方法,界面显示房屋列表信息。 编写addHouse()方法,界面接收用户输入,创建House对象,调用业务层add()方法。 编写delHouse()方法,界面接收用户输入的房屋id,调用业务层的del()方法。 编写updateHouse()方法,界面接收用户输入的房屋id,调用业务层的find()方法查找对象并修改。 编写findHouse()方法,界面接收用户输入的房屋id,调用业务层的find()方法。 编写exit()方法,界面接收用户输入的选择。

源码:

package com.yxz.house.view; import com.yxz.house.domain.House; import com.yxz.house.service.HouseService; import com.yxz.house.utils.Utility; /** * 1. View层,显示界面 * 2. 接收用户的输入 * 3. 调用HouseService完成对房屋信息的各种操作(CRUD) */ public class HouseView { // 属性 private boolean loop = true; // 控制显示菜单 private final HouseService houseService = new HouseService(2); // 声明一个HouseService对象,用于listHouses方法调用其list()方法 /** * 显示主菜单的方法 */ public void mainMenu() { do { System.out.println("===================房屋出租系统菜单==================="); System.out.println("\t\t\t\t 1. 新 增 房 源"); System.out.println("\t\t\t\t 2. 查 找 房 屋"); System.out.println("\t\t\t\t 3. 删 除 房 屋 信 息"); System.out.println("\t\t\t\t 4. 修 改 房 屋 信 息"); System.out.println("\t\t\t\t 5. 房 屋 列 表"); System.out.println("\t\t\t\t 6. 退 出 系 统"); System.out.print("请输入你的选择(1-6):"); // 接收用户选择 char key = Utility.readChar(); switch (key) { case '1' -> addHouse(); case '2' -> findHouse(); case '3' -> delHouse(); case '4' -> updateHouse(); case '5' -> listHouses(); case '6' -> exit(); default -> System.out.println("没有此功能,请重新选择...\n"); } } while (loop); } /** * 调用业务层的list()方法,界面显示房屋列表信息 */ public void listHouses() { System.out.println("-------------------房 屋 列 表 信 息------------------"); System.out.println("编号\t房主\t电话\t\t\t地址\t\t月租\t状态(未出租/已出租)"); House[] houses = houseService.list(); // 得到所有房屋信息 for (House house : houses) { if (house == null) { break; } System.out.println(house); } System.out.println("-------------------房屋列表显示完毕-------------------\n"); } /** * 界面接收用户输入,创建House对象,调用业务层add()方法 */ public void addHouse() { System.out.println("-------------------添 加 房 屋 信 息-------------------"); System.out.print("姓名: "); String name = Utility.readString(8); System.out.print("电话: "); String phone = Utility.readString(11); System.out.print("地址: "); String address = Utility.readString(16); System.out.print("月租: "); int rent = Utility.readInt(); System.out.print("状态: "); String state = Utility.readString(3); // 创建一个新的House对象, 注意id是系统分配的, House newHouse = new House(0, name, phone, address, rent, state); if (houseService.add(newHouse)) { System.out.println("-------------------添 加 房 屋 成 功-------------------\n"); } else { System.out.println("-------------------添 加 房 屋 失 败-------------------\n"); } } /** * 界面接收用户输入的房屋id,调用业务层的del()方法 */ public void delHouse() { System.out.println("-------------------删 除 房 屋 信 息------------------"); System.out.print("请输入待删除房屋的编号id(-1退出):"); int delId = Utility.readInt(); if (delId == -1) { System.out.println("-------------------放弃删除房屋信息-------------------\n"); return; } char choice = Utility.readConfirmSelection(); // 该方法有循环判断是否选择y/n的逻辑 if (choice == 'Y') { if (houseService.del(delId)) { System.out.println("------------------删 除 房 屋 成 功-------------------\n"); } else { System.out.println("--------------------房屋信息不存在--------------------\n"); } } else { System.out.println("-------------------放弃删除房屋信息-------------------\n"); } } /** * 根据id查找房屋信息,界面接受用户输入的房屋id,调用业务层find()方法 */ public void findHouse() { System.out.println("-------------------查 找 房 屋 信 息------------------"); System.out.print("请输入需要查找的房屋id:"); int findId = Utility.readInt(); // 调用find方法 House house = houseService.find(findId); if (house != null) { System.out.println(house); } else { System.out.println("--------------------房屋信息不存在--------------------\n"); } } /** * 修改房屋信息,界面接收要修改的房屋id,调用业务层的find()方法返回要修改的房屋对象并其修改属性 */ public void updateHouse() { System.out.println("-------------------修 改 房 屋 信 息------------------"); System.out.print("请选择待修改房屋编号(-1退出):"); int updateId = Utility.readInt(); if (updateId == -1) { System.out.println("-------------------放弃修改房屋信息-------------------\n"); return; } House house = houseService.find(updateId); if (house == null) { System.out.println("--------------------房屋信息不存在--------------------\n"); return; } System.out.print("姓名(" + house.getName() + "):"); String name = Utility.readString(8, ""); if (!"".equals(name)) { house.setName(name); } System.out.print("电话(" + house.getPhone() + "):"); String phone = Utility.readString(11, ""); if (!"".equals(phone)) { house.setPhone(phone); } System.out.print("地址(" + house.getAddress() + "):"); String address = Utility.readString(16, ""); if (!"".equals(address)) { house.setAddress(address); } System.out.print("租金(" + house.getRent() + "):"); int rent = Utility.readInt(-1); if (rent != -1) { house.setRent(rent); } System.out.print("状态(" + house.getState() + "):"); String state = Utility.readString(3, ""); if (!"".equals(state)) { house.setState(state); } System.out.println("-------------------修改房屋信息成功-------------------\n"); } /** * 退出房屋出租系统 */ public void exit() { // 使用工具类提供的方法 char c = Utility.readConfirmSelection(); if (c == 'Y') { loop = false; System.out.println("=================已退出房屋出租系统==================="); } } }

HouseService.java类[Service层,业务层]

定义House[],保存House对象。

响应HouseView的调用。 完成对房屋信息的各种操作(增删改查,C-Creat R-Read U-Update D-Delete)

代码实现分析:

编写list()方法,返回所有的房屋信息。 编写add(House newHouse)方法,把newHouse对象加入到houses数组,并返回bool值。 编写del(int delId)方法,完成真正的删除任务,返回bool值表示删除操作是否成功。 编写find(int findId)方法,查找指定id的房屋对象,如果有则返回对象,没有则返回null。

源码:

package com.yxz.house.service; import com.yxz.house.domain.House; /** * HouseService 业务层 * 定义House[],保存House对象。 * 1. 响应HouseView的调用。 * 2. 完成对房屋信息的各种操作(增删改查,C-Creat R-Read U-Update D-Delete) */ public class HouseService { private House[] houses; private int houseNums = 1; // 记录当前有多少房屋信息 private int idCounter = 1; // 记录当前id增长到哪个值 public HouseService(int size) { houses = new House[size]; // 当创建一个HouseService对象,指定数组大小 houses[0] = new House(1, "jack", "189xxxxxxxx", "上海市", 15000, "未出租"); } /** * list方法,返回所有房屋信息 * * @return :返回House[] */ public House[] list() { return houses; } /** * 添加新的房屋对象 * * @param newHouse :新增加的房屋信息 * @return :表示增加的操作是否成功 */ public boolean add(House newHouse) { // 判断是否可以继续添加 if (houseNums == houses.length) { // 数组已满,不能再添加 // 数组扩容 House[] tempHouses = new House[houses.length + 1]; // 创建一个临时数组,存放原houses数组的对象 System.arraycopy(houses, 0, tempHouses, 0, houses.length); houses = tempHouses; // 修改原数组引用houses指向的对象地址 } // 把newHouse对象加入到houses[] houses[houseNums++] = newHouse; // 程序员需要设计一个id自增长的机制,更新newHouse的id newHouse.setId(++idCounter); return true; } /** * 删除指定房屋id的房屋信息 * * @param delId :要删除房屋信息的房屋id * @return :表示房屋删除操作是否成功。 */ public boolean del(int delId) { // 先查询到要删除的房屋信息对应的下标,下标与房屋id不一致 int index = -1; for (int i = 0; i < houseNums; i++) { if (delId == houses[i].getId()) { // 要删除的房屋(id),是数组下标为i的元素 index = i; } } if (index == -1) { // 说明delId在数组中不存在 return false; } for (int i = index; i < houseNums - 1; i++) {// 从被删除的id开始,将后面的房屋信息前移 houses[i] = houses[i + 1]; } houses[--houseNums] = null; // 将此时的房屋数-1,再把当前最后一个房屋置空 return true; } /** * 根据用户输入的id,查找房屋信息 * * @param findId :房屋id * @return :表示查找到的房屋对象 */ public House find(int findId) { for (int i = 0; i < houseNums; i++) { if (findId == houses[i].getId()) { return houses[i]; } } return null; } }

House.java类[Model层,domain/数据层]

一个House对象表示一个房屋信息。

源码:

package com.yxz.house.domain; /** * House类的对象表示一个房屋信息 */ public class House { // 属性:编号、房主、电话、地址、月租、状态(未出租/已出租) private int id; private String name; private String phone; private String address; private int rent; private String state; // 构造器 和 setter/getter方法 public House(int id, String name, String phone, String address, int rent, String state) { this.id = id; this.name = name; this.phone = phone; this.address = address; this.rent = rent; this.state = state; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public int getRent() { return rent; } public void setRent(int rent) { this.rent = rent; } public String getState() { return state; } public void setState(String state) { this.state = state; } @Override public String toString() { return id + "\t\t" + name + "\t" + phone + "\t\t" + address + "\t\t" + rent + "\t" + state; } }

Utility.java类[Utility工具类]

在实际开发中,公司都会提供相应的工具类和开发库,可以提高开发效率。

了解Utility类的使用

Utility.java源码

/** * 工具类的作用: * 处理各种情况的用户输入,并且能够按照程序员的需求,得到用户的控制台输入。 */ import java.util.*; public class Utility { //静态属性 private static Scanner scanner = new Scanner(System.in); /** * 功能:读取键盘输入的一个菜单选项,值:1——5的范围 * * @return 1——5 */ public static char readMenuSelection() { char c; for (; ; ) { String str = readKeyBoard(1, false);//包含一个字符的字符串 c = str.charAt(0);//将字符串转换成字符char类型 if (c != '1' && c != '2' && c != '3' && c != '4' && c != '5') { System.out.print("选择错误,请重新输入:"); } else break; } return c; } /** * 功能:读取键盘输入的一个字符 * * @return 一个字符 */ public static char readChar() { String str = readKeyBoard(1, false);//就是一个字符 return str.charAt(0); } /** * 功能:读取键盘输入的一个字符,如果直接按回车,则返回指定的默认值;否则返回输入的那个字符 * * @param defaultValue 指定的默认值 * @return 默认值或输入的字符 */ public static char readChar(char defaultValue) { String str = readKeyBoard(1, true);//要么是空字符串,要么是一个字符 return (str.length() == 0) ? defaultValue : str.charAt(0); } /** * 功能:读取键盘输入的整型,长度小于2位 * * @return 整数 */ public static int readInt() { int n; for (; ; ) { String str = readKeyBoard(10, false);//一个整数,长度 Y n=>N String str = readKeyBoard(1, false).toUpperCase(); c = str.charAt(0); if (c == 'Y' || c == 'N') { break; } else { System.out.print("选择错误,请重新输入:"); } } return c; } /** * 功能: 读取一个字符串 * * @param limit 读取的长度 * @param blankReturn 如果为true ,表示 可以读空字符串。 * 如果为false表示 不能读空字符串。 *

* 如果输入为空,或者输入大于limit的长度,就会提示重新输入。 * @return */ private static String readKeyBoard(int limit, boolean blankReturn) { //定义了字符串 String line = ""; //scanner.hasNextLine() 判断有没有下一行 while (scanner.hasNextLine()) { line = scanner.nextLine();//读取这一行 //如果line.length=0, 即用户没有输入任何内容,直接回车 if (line.length() == 0) { if (blankReturn) return line;//如果blankReturn=true,可以返回空串 else continue; //如果blankReturn=false,不接受空串,必须输入内容 } //如果用户输入的内容大于了 limit,就提示重写输入 //如果用户如的内容 >0 limit) { System.out.print("输入长度(不能大于" + limit + ")错误,请重新输入:"); continue; } break; } return line; } }

测试Utility类

HouseRentApp.java程序入口, 直接创建对象调用方法

源码:

package com.yxz.house; import com.yxz.house.view.HouseView; public class HouseRentApp { public static void main(String[] args) { // 创建HouseView对象,并显示主菜单 -> 程序(系统)入口 new HouseView().mainMenu(); } } 第二阶段

第二阶段 目标:提升编程能力

1、面向对象编程(高级) 1.1、类变量和类方法

类变量和类方法使用static关键字修饰,也称静态变量(静态属性)和静态方法。类变量是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。

定义类变量/类方法的语法:

访问修饰符 static 数据类型 变量名;// 推荐使用 static 访问修饰符 数据类型 变量名; 访问修饰符 static 数据返回类型 方法名() {} // 推荐使用 static 访问修饰符 数据返回类型 方法名() {}

如何访问类变量/类方法:(静态变量/静态方法的访问修饰符的访问权限和范围与普通成员一样,遵守访问规则)

类名.类变量名; // 推荐使用 对象名.类变量名; 类名.类方法名(); // 推荐使用 对象名.类方法名();

特点:

类变量和类方法会被该类的所有的对象实例共享。 static类变量在类加载时就生成并初始化,即使没有创建对象,只要类加载了,就可以使用类变量。 在JDK8之前的版本中,静态变量是存放在方法区的静态域中;在JDK8之后的版本中,方法区中的类信息加载后会在堆中开辟属于该类的一个Class对象实例,属于该类的静态变量会存放在Class对象实例的尾部。 类变量的生命周期是随着类的加载开始,随着类消亡而销毁。 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区。 类方法中不能使用和对象有关的关键字,如:this,super。 类方法中只能访问静态变量或静态方法。 普通成员方法既可以访问非静态变量(方法),也可以访问静态变量(方法)。 静态方法可以被子类继承,但是不能被子类重写(覆盖) -> 静态方法属于类成员。

何时使用类变量/类方法:

当一个变量需要该类所有对象实例共享时,使用类变量。 当方法中不涉及到任何和对象相关的成员,可以将方法设计成静态方法,提高开发效率 -> 不希望创建对象实例,也可以调用方法时(当作工具使用)。 1.2、理解main方法语法 public static void main(String[] args){}

说明:

main()方法是Java虚拟机调用。

Java虚拟机需要调用类的main()方法 -> main()方法的访问权限必须是public。

Java虚拟机在执行main()方法时不必创建对象 -> main()方法必须是static。

main()方法接收String类型的数组参数,该数组中保存执行Java命令时所传递给运行的类的参数。

语法:java 执行的程序(带有main()方法的程序) 参数1 参数2 参数3...。如图:

在main()方法中,可以直接调用main()方法所在类的静态方法和静态属性。但是不能直接访问该类的非静态成员,要创建该类的对象实例去访问。

在IDEA中,传入main()方法的参数:构建 -> 编辑配置 -> 对应程序

1.3、代码块

代码化块又称为初始化块,属于类中的成员 -> 类的一部分,类似于方法,将逻辑语句封装在方法体中,通过{}包装起来。

但和方法不同,没有方法名,没有返回值类型,没有参数,只有方法体,而且不能通过对象或类显示调用,而是在加载类或创建对象时隐式调用。

基本语法:

[修饰符] { 代码; };

说明:

修饰符要么static,要么不写。 代码块分为两类。使用static修饰的叫静态代码块,没有static修饰的叫普通代码块。 代码可以为任何逻辑语句(输入、输出、方法调用、控制结构等)。 结尾的;可写可不写。

代码块的好处:

相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作。 如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性。 代码块的调用优先于构造器,不管调用哪个构造器,都会先调用代码块的内容。

注意:

static代码块的作用是对类进行初始化,static代码块随着类的加载而执行且只会执行一次。如果是非静态代码块,每创建一个对象就执行一次。

类什么时候被加载:

创建对象实例时(new)。 创建子类对象实例时,父类也会被加载。 使用类的静态成员时(静态变量、静态方法、静态代码块)。

非静态代码块,在创建对象实例时会被隐式的调用。如果只是使用类的静态成员时,普通代码块并不会执行。

创建一个对象实例时,在一个类的调用顺序:静态(代码块、属性) -> 非静态(代码块、属性) -> 构造器

调用静态代码块和静态属性初始化。静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按照从上往下定义的顺序调用。 调用普通代码块和普通属性的初始化。普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,按照从上往下定义的顺序调用。 调用构造方法。 package com.yxz.oop_high; public class CodeBlock { public static void main(String[] args) { // 假设以下每次运行时只执行一个输出语句 // System.out.println(A.age); /* 分析:加载一次类信息,访问静态成员,不创建对象实例 1. 调用静态代码块,输出:A的静态代码块 被调用 2. 调用静态变量,输出:100 */ // System.out.println(new A().name); /* 分析:加载一次类信息,创建对象实例,访问非静态成员 1. 加载类信息,调用静态代码块,输出:A的静态代码块 被调用 2. 创建对象实例,调用A类的普通代码块,输出:A的非静态代码块 被调用 3. 调用A的构造器,实例化对象,输出:A的构造器 被调用 4. 访问普通成员,输出:abc */ // System.out.println(B.salary); /* 分析:加载一次类信息,调用静态成员,不创建对象实例 1. 先调用父类的静态代码块,完成父类初始化,输出:A的静态代码块 被调用 2. 加载类信息,调用本类的静态代码块,完成子类初始化,输出:B的静态代码块 被调用 3. 访问静态变量,输出:1314.52 */ System.out.println(new B().count); /* 分析:加载一次类信息,创建对象实例,访问非静态成员 1. 先完成父类信息的加载,调用父类的静态代码块,输出:A的静态代码块 被调用 2. 完成子类信息的加载,调用本类的静态代码块,输出:B的静态代码块 被调用 3. 需要创建对象实例,先完成父类非静态信息加载,输出:A的非静态代码块 被调用 4. 要创建子类对象,先调用父类构造器完成父类的初始化,输出:A的构造器 被调用 5. 需要创建本类对象实例,先完成本类的非静态信息加载,输出:B的非静态代码块 被调用 6. 调用完成子类构造器,完成子类对象初始化,输出:B的构造器 被调用 7. 访问本类的非静态信息,输出:10 */ } } class A { public static int age = 100; // 静态变量 static { // this.age = 120; // 错误,静态代码块内不能使用this/super和对象有关的关键字 System.out.println("A的静态代码块 被调用..."); } public String name = "abc"; { // 非静态代码块在创建对象实例时会被调用 System.out.println("A的非静态代码块 被调用..."); } public A() { // 默认隐藏的语句 // 1. super(); // 2. 普通代码块和普通属性的初始化; System.out.println("A的构造器 被调用..."); } } class B extends A { public static double salary = 1314.520; // 静态变量 static { System.out.println("B的静态代码块 被调用..."); } public int count = 10; { System.out.println("B的非静态代码块 被调用..."); } public B() { // 默认隐藏的语句 // 1. super(); // 2. 普通代码块和普通属性的初始化; System.out.println("B的构造器 被调用..."); } }

创建一个子类对象时(继承关系),他们的静态代码块、静态属性初始化、普通代码块、普通属性初始化、构造器的调用顺序:

父类的静态代码块和静态属性(优先级一样,按定义顺序执行)。 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)。 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)。 父类的构造器。 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)。 子类的构造器。 package com.yxz.oop_high; public class CodeBlockDetail { public static void main(String[] args) { new B02(); } } class A02 { //父类 private static int n1 = getVal01(); static { System.out.println("A02的一个静态代码块..");//(2) } public int n3 = getVal02();//普通属性的初始化 { System.out.println("A02的一个普通代码块..");//(5) } public A02() {//构造器 //隐藏 //super() //普通代码和普通属性的初始化...... System.out.println("A02的构造器");//(7) } public static int getVal01() { System.out.println("A02的静态方法getVal01");//(1) return 10; } public int getVal02() { System.out.println("A02的普通方法getVal02");//(6) return 10; } } class B02 extends A02 { // 子类 private static int n3 = getVal03(); static { System.out.println("B02的一个静态代码块..");//(4) } public int n5 = getVal04(); { System.out.println("B02的一个普通代码块..");//(9) } //一定要慢慢的去品.. public B02() {//构造器 //隐藏了 //super() //普通代码块和普通属性的初始化... System.out.println("B02的构造器");//(10) } public static int getVal03() { System.out.println("B02的静态方法getVal03");//(3) return 10; } public int getVal04() { System.out.println("B02的普通方法getVal04");//(8) return 10; } }

静态代码块只能调用静态成员,普通代码块可以调用任意属性。

1.4、单例设计模式

设计模式(共23种):在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式。

单例(单个的实例)模式(singleton pattern):采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。

单例模式(8种)的其中两种方式:饿汉式、懒汉式。

饿汉式:在类中创建静态对象 -> 当加载类信息时就会自动创建该对象。即有可能还不需要使用该对象,但是类对象已经创建完毕 -> 很着急的饿汉

实现步骤:

构造器私有化 -> 防止用户使用new创建对象。 类的内部创建对象。为了能够在静态方法中访问该对象,需要使用static修饰该对象 -> 该类的内部对象是static的 向外暴露一个静态的公共方法getInstance,返回一个类的对象实例。

代码实现:

package com.yxz.oop_high.single_; /** * 实现饿汉式的单例模式 */ public class SingleTon01 { public static void main(String[] args) { // System.out.println(GirlFriend.n1); //当访问类变量时,就已经加载了类信息 -> 静态对象已经创建完毕 GirlFriend girlFriend1 = GirlFriend.getInstance(); GirlFriend girlFriend2 = GirlFriend.getInstance(); System.out.println(girlFriend1 == girlFriend2); // true,是同一个对象地址 } } /** * 只能有一个女朋友(单例模式) */ class GirlFriend { // public static int n1 = 100; // 为了保证在静态方法getInstance()中能够返回girlFriend对象,要用static修饰 private static GirlFriend girlFriend = new GirlFriend("宝贝"); // 第二步:在类的内部直接创建 private String name; // 如何保证用户只能创建一个 GirlFriend 对象 // 步骤[单例模式-饿汉式] // 1.构造器私有化 // 2.在类的内部直接创建 // 3.提供一个公共的static方法,返回一个GirlFriend对象 private GirlFriend(String name) { // 第一步:构造器私有化 // System.out.println("构造器被调用..."); this.name = name; } public static GirlFriend getInstance() { // 第三步:提供一个公共的static方法,返回一个GirlFriend对象 return girlFriend; // 返回一个对象 } @Override public String toString() { return "GirlFriend{" + "name='" + name + '\'' + '}'; } }

饿汉式缺点:当访问类的其他静态成员时,不需要创建该类对象,但是该类静态对象已经在第一次加载类信息时已经创建完毕 -> 可能造成已经创建了对象,但是没有使用的问题。

懒汉式:现在类中声明一个静态对象属性但不实例化对象,在公共的静态方法中创建对象 -> 只有使用getInstance方法时才创建对象 -> 懒汉

实现步骤:

构造器私有化 -> 防止用户使用new创建对象。 类的内部定义一个静态属性对象但不实例化。 向外暴露一个静态的公共方法getInstance,创建并返回一个类的对象实例。只有第一次调用该方法时才创建对象,否则还返回上次创建的对象。

代码实现:

package com.yxz.oop_high.single_; /** * 实现懒汉式的单例模式 */ public class SingleTon02 { public static void main(String[] args) { // System.out.println(BoyFriend.n1); // 访问其他类成员时,加载类信息但不会创建对象 BoyFriend boyFriend1 = BoyFriend.getInstance(); System.out.println(boyFriend1); BoyFriend boyFriend2 = BoyFriend.getInstance(); System.out.println(boyFriend1 == boyFriend2); // true; } } /** * 只能有一个男朋友(单例模式) */ class BoyFriend { // public static int n1 = 100; private static BoyFriend boyFriend; // 第二步:定义一个static静态属性对象,此时对象置空 private String name; // 步骤[单例模式-懒汉式] // 1.构造器私有化 // 2.定义一个static静态属性对象 // 3.提供一个公共的static方法,可以放回一个BoyFriend对象 private BoyFriend(String name) { // 第一步:构造器私有化 // System.out.println("构造器被调用..."); this.name = name; } public static BoyFriend getInstance() {// 第三步:提供一个公共的static方法,返回一个GirlFriend对象 if (boyFriend == null) { // 判断是否创建了对象 boyFriend = new BoyFriend("亲爱的"); } return boyFriend; } @Override public String toString() { return "BoyFriend{" + "name='" + name + '\'' + '}'; } }

饿汉式和懒汉式的区别:

创建对象时机不同。饿汉式是在类加载就创建了对象实例,懒汉式是在使用时才创建。 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。 饿汉式存在资源浪费的可能,懒汉式不存在资源浪费问题。 在JavaSE标准类中,java.lang.Runtime是经典的单例模式。 1.5、final 关键字

final关键字可以修饰类、属性、方法和局部变量。

适用场景(作用):

final修饰类:该类无法被继承 -> final修饰的类没有子类。 final修饰方法:该方法无法被子类覆盖/重写(override)。 final修饰类的属性:该属性的值无法被修改。 final修饰局部变量:该局部变量的值无法修改。

注意:

final修饰的属性又叫常量属性,一般用XX_XX_XX来命名(全部大写字母)。

final修饰的属性必须赋初值,并且以后不能在修改,赋值可以在如下位置:

定义时赋值。 在普通代码块中。 在构造器中。

如果final修饰的属性是静态的,则初始化的位置只能是:

定义时。 在静态代码块中。

final类不能被继承,但是可以实例化对象。

如果类不是final类,但是含有final方法,则该final方法虽然不能重写,但是可以被继承(遵守继承机制)。

一般地,如果一个类是final类,则没必要将该类的方法修饰成final方法 -> final类无子类,不会重写方法。

final不能修饰构造器。

final和static往往搭配使用,效率更高,调用final static修饰的成员时不会导致类加载,底层编译器做了优化处理。

public class Final01 { public static void main(String[] args) { System.out.println(A.n1); // 输出内容:10000 } } class A { public final static int n1 = 10000; static { System.out.println("A 的静态代码块被执行"); // 不会执行该语句 } }

包装类(Integer、Double、Float、Long、Boolean、String、Byte等)都是final类,不能被继承 -> 包装类没有子类。

1.6、抽象类

当父类的某些方法,需要声明并让子类继承,但是又不确定方法如何具体实现时,可以将其声明为抽象方法(abstract method),由子类重写实现该方法,那么这个类就是抽象类(abstract class)。

说明:

使用abstract关键字来修饰一个类时,该类就叫抽象类。

抽象类的基本语法:访问修饰符 abstract 类名{}

使用abstract关键字来修饰一个方法时,该方法就叫抽象方法 -> 抽象方法没有方法体。

基本语法:访问修饰符 abstract 返回值类型 方法名(参数列表);

抽象类的价值更多作用在于设计,设计者设计好后让子类继承并实现抽象类。

抽象类是面试官常问知识点,在框架和设计模式使用较多。

注意:

抽象类不能被实例化 -> 不能创建抽象类的对象。

抽象类不一定要包含抽象方法,还可以有实现的方法,-> 抽象类可以没有abstract方法。

抽象方法一定在抽象类里面 -> 一旦类包含了抽象方法,则这个类必须声明为abstract。

abstract只能修饰类和方法,不能修饰属性和其他的。

抽象类可以有任意成员(抽象类本质还是类),比如:非抽象方法、构造器、静态属性等。

抽象方法不能有方法体。

如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非这个类也声明为abstract类。所谓实现方法就是有方法体。

抽象方法不能使用private、final和static来修饰,因为这些关键字都是和方法重写机制相违背。

private修饰的方法不能被子类访问 -> 子类无法重写父类private方法 -> 和abstract方法要求子类继承并重写实现方法的本意违背

final修饰的方法不能被子类继承 -> 子类无法继承父类final方法 -> 和abstract方法要求子类继承并重写实现方法的本意违背

static修饰的方法属于类方法且不能被子类重写 -> 不创建类对象也能使用类名调用的方法 -> 和abstract方法没有方法体相悖

抽象类的最佳实践-模板设计模式

任务需求:

有多个类,完成不同的任务job 要求能够得到各自完成任务的时间。

方案一:基本思维流程:在不同类的job方法里面计算时间

A.java类文件,完成任务1

package com.yxz.oop_high.abstract_; // 任务1:计算1+2+...+100000000所花费的时间 public class A { public void job() { // 得到开始的时间 long start = System.currentTimeMillis(); long num = 0; for (int i = 1; i < 100000000; i++) { num += i; } // 得到结束的时间 long end = System.currentTimeMillis(); System.out.println("执行的时间: " + (end - start)); } }

B.java类文件,完成任务2

package com.yxz.oop_high.abstract_; // 任务2:计算1*2*...*100所花费的时间 public class B { public void job() { // 得到开始的时间 long start = System.currentTimeMillis(); long num = 1; for (int i = 1; i < 10000000; i++) { num *= i; } // 得到结束的时间 long end = System.currentTimeMillis(); System.out.println("执行的时间: " + (end - start)); } }

方案二:将方案一的代码重复部分抽取出来封装成单独的方法calculateTimes(),然后在calculateTimes()方法中调用job()方法 -> 提高代码复用性

A.java类文件,完成任务1

package com.yxz.oop_high.abstract_; // 任务1:计算1+2+...+100000000所花费的时间 public class A { // 代码改进方案一:将重复的代码抽出来封装成一个新方法,在新方法中调用计算任务job,如下 public void calculateTimes() { long start = System.currentTimeMillis(); // 调用任务 job(); long end = System.currentTimeMillis(); System.out.println("执行的时间: " + (end - start)); } public void job() { long num = 0; for (int i = 1; i < 100000000; i++) { num += i; } } }

B.java类文件,完成任务2

package com.yxz.oop_high.abstract_; // 任务2:计算1*2*...*100所花费的时间 public class B { // 代码改进方案一:将重复的代码抽出来组成一个新方法,在新方法中调用计算任务job public void calculateTimes() { long start = System.currentTimeMillis(); // 调用任务 job(); long end = System.currentTimeMillis(); System.out.println("执行的时间: " + (end - start)); } public void job() { long num = 1; for (int i = 1; i < 10000000; i++) { num *= i; } } }

方案三:在方案二的基础上,抽象一个父类Template,在父类声明一个抽象方法job(),将子类的封装方法calculateTimes()放在父类中,并在该方法中调用抽象方法job()。根据继承机制,当子类重写实现了父类的抽象方法job(),也会继承父类的calculateTimes()方法,可以直接调用该方法实现模板设计模式。

Template.java模板类文件(父类)

package com.yxz.oop_high.abstract_; // 模板类 abstract public class Template { public abstract void job(); // 抽象方法,由子类重写实现 public void calculateTimes() { // 实现的方法 long start = System.currentTimeMillis(); job(); // 调用抽象方法,运行时会动态绑定子类重写的方法!!!! long end = System.currentTimeMillis(); System.out.println("执行的时间: " + (end - start)); } }

A.java类文件,子类 完成任务1的job()方法实现

package com.yxz.oop_high.abstract_; // 任务1:计算1+2+...+100000000所花费的时间 public class A extends Template { @Override public void job() { // 重写了父类的抽象方法 long num = 0; for (int i = 1; i < 100000000; i++) { num += i; } } }

B.java类文件,完成任务2

package com.yxz.oop_high.abstract_; // 任务2:计算1*2*...*100所花费的时间 public class B extends Template { @Override public void job() { // 重写了父类的抽象方法 long num = 1; for (int i = 1; i < 10000000; i++) { num *= i; } } }

Abstract01.java测试类

package com.yxz.oop_high.abstract_; public class Abstract01 { public static void main(String[] args) { A a = new A(); a.calculateTimes(); // 动态绑定机制 B b = new B(); b.calculateTimes(); // 动态绑定机制 } } 1.7、接口

接口(Interface)就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,根据具体情况把这些方法写出来。

接口的基本语法:

interface 接口名 { 属性 方法(1.抽象方法 2.默认实现方法 3.静态方法) } class 类名 implements 接口名 { 本类属性 本类方法 必须实现接口的所有抽象方法 }

说明:

在JDK7.0之前 接口里的所有方法都没有方法体 -> 都是抽象方法 在JDK8.0之后接口可以有静态方法,默认方法(需要使用default关键字修饰),即接口中只有静态方法和默认方法有方法体。 在接口中的抽象方法可以省略abstract关键字。

注意:

接口不能被实例化。

接口中的所有方法是public方法(可以省略public关键字),接口中抽象方法可以不用abstract修饰。

一个普通类实现接口,就必须将该接口的所有方法都实现。在IDEA中,将光标停在实现接口的类名上,使用Alt + Enter快捷键可以快速实现接口所有方法。

抽象类实现接口,可以不用实现接口的方法。

一个类可以同时实现多个接口。

接口中的属性只能是public static final属性,而且属性必须初始化。如:int n1 = 1; =等价于=> public final static int n1 = 1。

接口中属性和静态方法的访问形式:接口名.属性名, 接口名.方法名()

通过实现类的对象,调用接口中的默认方法。如果实现类重写了接口中的默认方法,则调用时还是调用重写后的默认方法(和继承的重写一致)。

interface A { int n = 10; // 等价于 public final static int n = 10; static void hi() { System.out.println("hi"); } public default void method() { // 默认方法必须有default关键字 System.out.println("默认方法1"); } default void method2() { // 省略了public关键字,权限依然是public System.out.println("默认方法2"); } } public class Test implements A { public static void main(String[] args) { A.hi(); System.out.println(A.n); } }

接口不能继承其他的类,但是可以继承多个别的接口。接口与接口之间使用extends继承,接口与类之间使用implements实现。

interface A {} interface B {} interface C extends A, B{} // 接口继承不能使用implements

接口的修饰符只能是public和默认,这点和类的修饰符一样。

接口 VS 继承

接口是对Java中单根继承机制的补充。

继承

当子类继承父类之后,子类就自动拥有父类的能力即父类所有(可以访问的)属性和方法。(先天拥有的能力)

继承的价值主要在于:解决代码的复用性和可维护性。

继承需要满足is-a的关系。

接口

当子类需要扩展功能,可以通过实现接口的方式来扩展。(后天学习拥有的能力)

接口的价值主要在于:设计,设计好各种规范(方法),让其它类实现这些方法。

接口比继承更加灵活,只需要满足like-a的关系。

接口在一定程度上实现代码解耦[接口的规范性+动态绑定机制]。

接口的多态特性

多态参数

接口引用可以指向实现了接口的类的对象实例 -> 接口名 接口类型的引用名 = new 实现接口的类名();

案例:手机类和相机类实现UsbInterface,可以将phone对象和camera对象传入接口引用.

UsbInterface.java接口文件

package com.yxz.oop_high.interface_; public interface UsbInterface { // 规定接口的相关方法 public void start(); public void stop(); }

Camera.java类文件

package com.yxz.oop_high.interface_; public class Camera implements UsbInterface { // 实现接口的所有抽象方法 @Override public void start() { System.out.println("相机开始工作..."); } @Override public void stop() { System.out.println("相机停止工作..."); } }

Phone.java类文件

package com.yxz.oop_high.interface_; // Phone类 实现UsbInterface -> 需要实现UsbInterface接口 规定/声明的方法 public class Phone implements UsbInterface { // 实现接口方法 @Override public void start() { System.out.println("手机开始工作..."); } @Override public void stop() { System.out.println("手机停止工作..."); } }

Computer.java类文件,创建work(UsbInterface usbInterface)方法 -> 根据传入的不同实现接口的类的对象,调用不同的方法 -> 接口的多态

package com.yxz.oop_high.interface_; public class Computer { // 编写一个方法,计算机工作 public void work(UsbInterface usbInterface) { // 将设备接入到计算机 // 通过接口,来调用方法 -> 接口的多态 usbInterface.start(); // 动态绑定机制:根据传入的不同实现接口的类的对象,调用不同的start方法 usbInterface.stop(); // 动态绑定机制:根据传入的不同实现接口的类的对象,调用不同的stop方法 } }

Interface01.java测试类

package com.yxz.oop_high.interface_; public class Interface01 { public static void main(String[] args) { // 创建对象 // Phone 实现了 UsbInterface 接口 Phone phone = new Phone(); // Camera 实现了 UsbInterface 接口 Camera camera = new Camera(); // 创建计算机 Computer computer = new Computer(); computer.work(phone); // 将手机接入计算机,然后工作 -> 多态+动态绑定机制 System.out.println("================="); computer.work(camera); // 将相机接入计算机,然后工作 -> 多态+动态绑定机制 // 接口的多态 // 接口类型的变量 phone2 可以指向实现了UsbInterface接口类的对象实例 UsbInterface phone2 = new Phone(); UsbInterface camera2 = new Camera(); } }

多态数组

案例:在Usb(接口)数组中,存放Phone和Camera对象,Phone类有一个特有方法call()。遍历Usb数组,如果是Phone对象,除了调用Usb接口定义的方法work()外,还要调用特有方法call()。

public class InterfacePloyArr { public static void main(String[] args) { // 接口数组,多态数组 Usb[] usbs = new Usb[2]; usbs[0] = new Phone(); // 接口多态 usbs[1] = new Camera(); // 接口多态 for(int i = 0; i < usbs.length; i++) { usbs[i].work(); // 动态绑定 // 进行类型判断,向下转型 if (usbs[i] instanceof Phone) { // 判断其运行类型是否为Phone ((Phone)usbs[i]).call(); // 向下转型 } } } } interface Usb { void work(); } class Phone implements Usb { // 特有方法call public void call() { System.out.println("手机可以打电话..."); } // 实现接口的方法 @Override public void work() { System.out.println("手机工作中..."); } } class Camera implements Usb { // 实现接口的方法 @Override public void work() { System.out.println("相机工作中..."); } }

接口存在多态传递现象

如果一个类实现了的接口继承了其他接口,那么该类相当于也实现了其他接口。如下:

public class InterfacePloyPass { public static void main(String[] args) { // 接口类型的变量可以指向实现了该接口的类的对象实例 IG ig = new Teacher(); // 如果IG 继承了 IH 接口,而Teacher类实现了 IG接口 // 那么 就相当于Teacher也实现了IH接口 -> 接口的多态传递现象 IH ih = new Teacher(); // 虽然Teacher类没有实现IH接口,但是IG接口继承了IH接口 } } interface IH { void hi(); } interface IG extends IH {} // 接口继承 class Teacher implements IG { @Override public void hi() {}// 要求必须实现接口的方法 } 1.8、内部类

内部类(inner class):一个类的内部又完整的嵌套了另一个类结构,被嵌套的类就是内部类(inner class),嵌套其他类的类称为外部类(outer class)。是类的第五大成员(类的五大成员:1.属性 2.构造器 3.成员方法 4.代码块 5.内部类)。内部类最大的特点就是可以直接访问外部类的私有属性,并且可以体现类与类之间的包含关系。

基本语法:

class Outer { // 外部类 class Inner { // 内部类 } } class Other { // (外部)其他类 }

内部类的分类

定义在外部类的局部位置上,如:方法体内

局部内部类(有类名)

局部内部类的位置通常在方法体内,也可以在代码块中。局部内部类本质上还是一个类 -> 也可以有类的五大成员。

特点:

可以直接访问外部类的所有成员,包括私有成员。

不能添加访问修饰符,因为它的地位就是一个局部变量 -> 局部变量不能用访问修饰符。但是可以用final修饰,不让其他内部类继承, 也可以使用abstract修饰成抽象局部内部类.

作用域:仅仅在定义它的方法体内或代码块中。

局部内部类访问外部类的成员的方式:直接访问。无需借助类名或对象。

外部类 -- 访问 --> 局部内部类的成员,方式:在含有局部内部类的方法中创建内部类对象,内部类对象调用内部类成员。在其他外部类创建该外部类对象然后调用含有内部类的方法。

外部其他类 -- 不能访问 --> 局部内部类(地位局部变量)。

如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用外部类名.this.成员访问。

外部类名.this本质上是一个外部类对象,哪个对象调用了含有内部类的方法,外部类名.this就指向了哪个对象。

局部内部类的测试代码

package com.yxz.oop_high.innerclass; public class LocalInnerClass { public static void main(String[] args) { Outer02 outer02 = new Outer02(); outer02.m1(); // 调用含有局部内部类的方法 System.out.println("outer02的hashCode = " + outer02); // 输出和内部类的方法中输出一样的hashCode -> 同一个对象 } } class Outer02 { // 外部类 private int n1 = 100; private int n2 = 300; public void m1() { // 方法 // 局部内部类是定义在外部类的局部位置,通常在方法体内 final class Inner02 { // 局部内部类(本质上还是一个类) private int n1 = 123; // 可以访问外部类的所有成员,包括私有的 public void f1() { System.out.println("n2 = " + n2); m2(); // 可以访问外部类的所有成员 System.out.println("内部类的n1 = " + n1); // 内部类和外部类的成员重名时,要访问外部类成员,使用外部类名.this.成员名 // Outer02.this本质就是外部类的对象,即哪个对象调用了含有内部类方法m1(),就指向哪个对象 System.out.println("外部类的n1 = " + Outer02.this.n1); System.out.println("Outer02.this的hashCode = " + Outer02.this); } } // 外部类在含有局部内部类的方法中可以创建局部内部类对象,然后调用方法即可 Inner02 inner02 = new Inner02(); inner02.f1(); // 调用内部类的方法 // class Inner03 extends Inner02{} // 无法继承final类 } private void m2() { System.out.println("m2()被调用..."); } }

匿名内部类(没有类名,非常常用)

匿名内部类本质:1.类 2.局部内部类(方法体中或者代码块中) 3.匿名,无法直接看到类名 4.是一个对象

匿名内部类的基本语法:

class Outer { // 外部类 public void method() { new 类名(参数列表) { // 基于类的匿名内部类 类体; }; new 接口() { // 基于接口的匿名内部类 类体; }; new 抽象类(参数列表) { // 基于抽象类的匿名内部类 类体; 必须实现抽象类的抽象方法; }; } }

匿名内部类的测试代码

package com.yxz.oop_high.innerclass; interface IA { void cry(); } /** * 匿名内部类的演示 */ public class AnonymousInnerClass { public static void main(String[] args) { Outer03 outer03 = new Outer03(); outer03.method(); outer03.method(); } } class Outer03 { private int n1 = 10; public void method() { // 方法 // 基于接口的匿名内部类 // 1.需求:想使用IA接口,并创建对象且只使用一次,不用类实现接口的方式 // 使用匿名内部类简化开发 // tiger的编译类型:IA // tiger的运行类型:匿名内部类:XXXX => Outer03$1 /* 匿名内部类Outer03$1的底层源码:由系统分配匿名内部类的类名 class Outer03$1 implements IA { @Override public void cry() { System.out.println("老虎叫..."); } } */ // JDK底层在创建匿名内部类Outer03$1时,立即马上创建了Outer03$1的对象实例,并且把对象地址返回给tiger // 匿名内部类使用一次就消失,不能再使用,但是接口引用指向的对象还存在 IA tiger = new IA() { @Override public void cry() { System.out.println("老虎叫..."); } }; System.out.println("tiger的运行类型:" + tiger.getClass()); tiger.cry(); // 基于类的匿名内部类 // 1. father的编译类型:Father // 2. father的运行类型:XXXX => 底层系统分配类名Outer03$2 // 3. 底层会创建匿名内部类Outer03$2 // 4. 同时返回了匿名内部类Outer03$2的对象实例给father // 5. 参数列表jack会自动传递给Father类的构造器 /* class Outer03$2 extends Father{ // 有继承关系 } */ Father father = new Father("jack") { @Override public void test() { // 动态绑定机制 System.out.println("匿名内部类重写了test方法"); } }; System.out.println("father的运行类型 = " + father.getClass()); father.test(); // 运行类型是匿名内部类Outer$2,调用test方法会动态绑定匿名内部类重写的test方法 // 基于抽象类的匿名内部类 // 编译类型:Animal // 运行类型:匿名内部类XXXX => 由系统底层分配类名Outer03$3 Animal animal = new Animal() { @Override void eat() { System.out.println("小狗狗吃东西"); } }; animal.eat(); /* 底层本质: class 匿名内部类 extends Person {} */ new Person() { // 匿名内部类本身也是返回对象 @Override public void hi() { System.out.println("子类重写的hi()方法,n1 = " + n1); } }.hi(); // 本质:匿名内部类的对象调用方法,动态绑定子类(匿名内部类Outer03$4)的hi()方法 } } class Father { public Father(String name) { System.out.println("Father构造器被调用,接收到name = " + name); } public void test() { } } abstract class Animal { abstract void eat(); }

注意:

匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征。 创建匿名内部类的最后有一个分号;相等于创建对象的语句。 如果在匿名内部类重写了接口的方法或者父类的方法,在使用匿名内部类对象调用方法时会有动态绑定机制。 Java底层在创建匿名内部类时,会给匿名内部类分配类名,创建完之后会立即返回一个匿名内部类对象。 可以访问外部类的所有成员,包含私有的。 不能添加访问修饰符,地位是局部变量。 作用域:仅仅在定义它的方法或代码块中。 外部其他类 -- 不能访问 --> 匿名内部类。 如果外部类和匿名内部类的成员同名时,匿名内部类要访问外部类的同名成员,方法和局部内部类一样:外部类名.this.成员访问

匿名内部类的实践:当作实参传递,更加简洁高效

案例一:

package com.yxz.oop_high.innerclass; public class InnerClassExercise01 { public static void main(String[] args) { test(new A() { // 将匿名内部类当作实参使用 -> 匿名内部类本质上返回一个对象 @Override public void show() { System.out.println("匿名内部类实现了接口的方法"); } }); // 传统方法(硬编码) -> 死板,不灵活 test(new Picture()); } static void test(A a) { a.show(); } } interface A { void show(); } // 传统方式 实现接口 => 编程领域的硬编码 class Picture implements A { @Override public void show() { System.out.println("一幅名画..."); } }

案例二:

package oop_high_Exercise; // 接口类 interface Computer { // work方法是完成计算,如何计算交给匿名内部类完成 double work(double d1, double d2); } public class Homework04 { public static void main(String[] args) { CellPhone cellPhone = new CellPhone(); cellPhone.testWork(new Computer() {// 将匿名内部类当成对象传递给方法 @Override public double work(double d1, double d2) { return d1 + d2; } }, 99, 999); cellPhone.testWork(new Computer() {// 将匿名内部类当成对象传递给方法 @Override public double work(double d1, double d2) { return d1 * d2; } }, 520, 1314); } } class CellPhone { // 当调用testWork方法时,直接传入一个实现了Computer接口的匿名内部类即可 // 该匿名内部类可以灵活的实现work方法,完成不同的计算任务 public void testWork(Computer computer, double d1, double d2) { double res = computer.work(d1, d2); // 动态绑定匿名内部类重写的方法 System.out.println("运算的结果 = " + res); } }

定义在外部类的成员位置上:

成员内部类(不用static修饰)

成员内部类是定义在外部类的成员位置,并且没有static修饰。

成员内部类的基本语法:

class Outer { // 外部类 class Inner { // 成员内部类 类体; } }

注意:

可以直接访问外部类的所有成员,包含私有属性。 可以添加任意访问修饰符,因为它的地位是类的成员。 作用域:和外部类其他成员一样,为整个类体。在外部类的成员方法中创建成员内部类对象,再调用方法。 成员内部类 -- 访问 --> 外部类,方式:直接访问 外部类 -- 访问 --> 内部类,访问方式:在外部类的成员方法中创建内部类对象,再调用方法。 外部其他类 -- 访问 --> 成员内部类,通过内部类对象访问,创建内部类对象的两种方式: 使用外部类对象new一个内部类对象,外部类名.内部类名 引用 = 外部类对象.new 内部类名(); 在外部类中编写一个方法,方法返回类型是内部类对象,接收该方法返回值得到内部类的对象实例。 如果外部类和内部类的成员重名时,内部类访问遵循就近原则,如果内部类想要访问外部类的重名成员,使用外部类名.this.成员访问。

成员内部类的测试代码

package com.yxz.oop_high.innerclass; public class MemberInnerClass01 { public static void main(String[] args) { Outer05 outer05 = new Outer05(); outer05.t1(); // 外部其他类,使用成员内部类的三种方式 // 第一种方式:使用外部类对象new一个内部类对象 Outer05.Inner05 inner05 = outer05.new Inner05(); // 第二种方式:在外部类中编写一个方法,方法返回内部类对象 Outer05.Inner05 inner05Instance = outer05.getInner05Instance(); } } class Outer05 { private int n1 = 1000; private String name = "bbq"; private void hi() { System.out.println("hi()方法"); } // 成员方法 public void t1() { // 创建成员内部类对象 Inner05 inner05 = new Inner05(); inner05.say(); System.out.println(inner05.sal); } public Inner05 getInner05Instance() { return new Inner05(); } // 成员内部类 public class Inner05 { public double sal = 99.8; private int n1 = 5; public void say() { // 可以直接访问外部类的所有成员 System.out.println("n1 = " + n1 + " name = " + name); // 访问所有外部类成员 hi(); // 访问外部类重名成员,使用外部类名.this.成员访问 System.out.println("外部类成员n1 = " + Outer05.this.n1); } } }

静态内部类(使用static修饰)

静态内部类是定义在外部类的成员位置,并且有static修饰,地位相当于静态变量。

说明:

可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员。 可以添加任意访问修饰符。 作用域:同其他类成员,整个类体。 静态内部类 -- 访问 --> 外部类(静态属性),访问方式:直接访问。 外部类 -- 访问 --> 静态内部类,访问方式:在外部类的方法中创建内部类对象,再调用外部类方法。 外部其他类 -- 访问 --> 静态内部类, 如果外部类和静态内部类的成员重名时,静态内部类要访问外部类同名(静态)属性,使用外部类名.属性访问。静态内部类不能直接访问外部类非静态成员(需要创建外部类对象)。

静态内部类的测试代码:

package com.yxz.oop_high.innerclass; public class StaticInnerClass01 { public static void main(String[] args) { Outer06 outer06 = new Outer06(); outer06.m1(); // 外部其他类使用静态内部类, // 方式1:相当于调用类的静态变量,可以通过类名直接访问(遵守访问权限) Outer06.Inner06 inner06 = new Outer06.Inner06(); inner06.say(); // 方式2:编写一个(静态)方法,方法返回静态内部类的对象实例 Outer06.Inner06 inner06Instance = outer06.getInner06Instance(); // 外部类对象调用非静态方法 Outer06.Inner06 inner06Instance_ = Outer06.getInner06Instance_(); // 外部类调用静态方法 } } class Outer06 { private static String name = "张三"; private int n1 = 10; public static void cry() { } public void m1() { // 外部类访问静态内部类:先创建对象,在调用方法 Inner06 inner06 = new Inner06(); inner06.say(); } public Inner06 getInner06Instance() { return new Inner06(); } public static Inner06 getInner06Instance_() { return new Inner06(); } // 成员 静态内部类 static class Inner06 { public void say() { // System.out.println(n1); 错误,静态内部类不能直接访问外部类的非静态成员 System.out.println(name); // 可以访问外部类的所有静态成员 cry(); Outer06 outer06 = new Outer06(); } } } 2、枚举和注解 2.1、自定义类实现枚举

枚举(enumeration, 简写enum)是一组常量的集合。枚举属于一种特殊的类,里面只包含一组有限的特定的对象。

如何实现自定义枚举类:

构造器私有化 -> 防止直接创建对象。 去掉类的相关set方法 -> 防止属性值被修改。 在类的内部,直接创建静态对象。 优化,使用final关键字修饰静态对象。 package com.yxz.enum_; public class Enumeration01 { public static void main(String[] args) { System.out.println(Season.SPRING); } } class Season { private final String name; private final String desc; // 描述 // 定义四个固定的对象 public static final Season SPRING = new Season("春天", "温暖"); public static final Season SUMMER = new Season("夏天", "炎热"); public static final Season AUTUMN = new Season("秋天", "凉爽"); public static final Season WINTER = new Season("冬天", "寒冷"); private Season(String name, String desc) { this.name = name; this.desc = desc; } public String getName() { return name; } public String getDesc() { return desc; } @Override public String toString() { return "Season{" + "name='" + name + '\'' + ", desc='" + desc + '\'' + '}'; } } 2.2、enum关键字实现枚举

步骤:

使用enum关键字替代class。 将创建静态常量对象的语句改成常量名(实参列表);,相当于简化了代码,底层还是会调用构造器。 如果有多个常量(对象),使用,间隔即可。 如果使用enum实现枚举,需要将常量对象写在最前面。 package com.yxz.enum_; public class Enumeration02 { public static void main(String[] args) { System.out.println(Season.SPRING); System.out.println(Season.SUMMER); Season a = Season.SPRING; Season b = Season.SPRING; System.out.println(a == b); // true,同一个静态常量对象 } } enum Season { // 枚举类 // 枚举对象写在最前面 SPRING("春天", "温暖"), SUMMER("夏天", "炎热"), AUTUMN("秋天", "凉爽"), WINTER("冬天", "寒冷"); private final String name; private final String desc; // 描述 private Season(String name, String desc) { this.name = name; this.desc = desc; } public String getName() { return name; } public String getDesc() { return desc; } @Override public String toString() { return "Season{" + "name='" + name + '\'' + ", desc='" + desc + '\'' + '}'; } }

注意:

当使用enum关键字实现一个枚举类时,默认会继承java.lang.Enum类,而且枚举类会自动变成一个final类。使用javap指令反编译查看源码。 传统方式实现枚举类中定义对象的语句public static final Season SPRING = new Season("春天", "温暖");简化成SPRING("春天", "温暖")仍然会调用构造器 -> 枚举类需要提供构造器。 如果使用无参构造器创建枚举对象,则实参列表和小括号都可以省略。 当有多个枚举对象时,使用,间隔,最后有一个;结尾。 枚举对象必须放在枚举类的行首。 使用enum实现的枚举类的所有构造器必须是private的,默认是private的 -> 构造器的修饰符可以不写。 enum实现的枚举类中的常量对象都是静态对象 -> 同一个常量对象被所有枚举类的对象共享。 使用enum关键字后就不能再继承其他类,因为enum修饰的类会隐式继承Enum类,Java是单根继承继承,可以使用接口。

枚举类和switch的搭配使用

基本语法:

switch (枚举类对象) { case 枚举常量值1: 语句; break; case 枚举常量值2: 语句; break; case 枚举常量值3: 语句; break; ...... }

案例:枚举类Color,五种颜色的枚举对象,实现接口的show()方法,打印枚举对象的三个属性值。

package oop_high_Exercise; enum Color implements ColorInterface { RED(255, 0, 0), BLUE(0, 0, 255), BLACK(0, 0, 0), YELLOW(255, 255, 0), GREEN(0, 255, 0); int redValue; int greenValue; int blueValue; Color(int redValue, int greenValue, int blueValue) { this.redValue = redValue; this.greenValue = greenValue; this.blueValue = blueValue; } @Override public void show() { System.out.println("redValue = " + redValue + ", greenValue = " + greenValue + ", blueValue = " + blueValue); } } interface ColorInterface { void show(); } public class Homework08 { public static void main(String[] args) { Color green = Color.GREEN; green.show(); switch (green) { case RED -> System.out.println("红色"); case BLUE -> System.out.println("蓝色"); case BLACK -> System.out.println("黑色"); case GREEN -> System.out.println("绿色"); case YELLOW -> System.out.println("黄色"); default -> System.out.println("颜色错误"); } } }

enum常用方法

toString():Enum类已经重写,返回的是当前对象名,子类可以重写该方法。 name():返回当前常量对象(枚举对象)名,子类不能重写该方法。 ordinal():返回的是该枚举对象的次序/编号,从0开始编号。 values():返回当前枚举类所有枚举对象。 valueOf(String str):根据传入的字符串去枚举类中匹配枚举对象名,如果匹配成功则返回对应的枚举对象,否则抛出异常。 compareTo():比较两个枚举对象,比较的就是枚举对象的次序/编号(从0开始,ordinal()方法返回的编号),返回两个对象的编号差值。 2.3、JDK内置的基本注解类型

注解(Annotation)也被称为元数据(Metadata),用于修饰解释包、类、方法、属性、构造器、局部变量等数据信息。

注解和注释一样,注解不影响程序的逻辑,但注解可以被编译或运行,相当于在代码中的补充信息。

在JavaSE中,注解的使用目的比较简单,如标记过时的功能,忽略警告等。在JavaEE中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。

使用Annotation时要在其前面增加@符号,并把该Annotation当成一个修饰符使用,用于修饰它支持的程序元素。

@Override:限定某个方法,是重写父类方法,该注解只能用于方法(继承时已讲)。

添加@Override注解,编译器会对该方法进行校验(从编译层面验证) -> 判断该方法是否真的重写父类的方法,如果不构成重写,则编译错误。

@Override的源码:

package java.lang; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }

说明:@interface不是interface,表示一个注解类。是JDK5.0后加入的。

@Target(ElementType.METHOD)说明该@Override注解只能修饰方法。@Target是修饰注解的注解,称为元注解。

@Deprecated:用于表示某个程序元素(类、方法等)已过时即不推荐使用,但是不代表不能使用。

@Deprecated class A { @Deprecated public int n1 = 10; @Deprecated public void hi(){} }

@Deprecated的源码:

package java.lang; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; @Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE}) public @interface Deprecated { String since() default ""; boolean forRemoval() default false; }

@Deprecated可以修饰构造器、字段、局部变量、方法、包、参数等等。可以做版本升级过度使用。

@Suppresswarnings:抑制编译器警告。

抑制的信息种类根据传入的参数决定。

@SuppressWarnings抑制警告的范围和其放置位置有关

public class Test { @SuppressWarnings("all") // 抑制所有警告信息 public static void main(String[] args) { int i = 0; int b = 10; System.out.println(b); } }

@SuppressWarnings的源码:

package java.lang; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); // 传入数组,对多种类型的警告进行抑制 } 2.4、元注解

元注解是修饰注解的注解。

元注解种类:

@Retention

只能用于修饰一个注解,指定注解的作用范围,@Rentention包含一个RetentionPolicy类型的成员变量,使用时必须为该value成员变量指定值(SOURCE,CLASS,RUNTIME)

RetentionPolicy.SOURCE:编译器使用后,直接丢弃的注解。

RetentionPolicy.CLASS:编译器将注解记录在class文件中,当运行Java程序时,JVM不会保留注解(默认值)。

RetentionPolicy.RUTIME:编译器将把注解记录在class文件中,当运行Java程序时,JVM会保留注解,程序可以通过反射获取该注解。

@Target:指定注解可以在哪些地方使用。

@Documented:指定该注解是否会在javadoc体现。即在生成文档时,可以看到该注解。定义为Documented的注解必须设置Retention值为RUNTIME。

@Inherited:子类会继承父类注解。

3、异常 3.1、异常的概念

当程序出现了问题,不应该就终止运行,使用针对性的代码进行特殊处理(try-catch)来保证程序即使出现异常也能正常运行 -> 保证程序的健壮性。

在IDEA中,将可能抛出异常的代码选中,使用快捷键:Alt+Shift+T,选择try-catch,可以快速生成异常处理try-catch的代码。

在Java语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常)

执行过程中所发生的异常事件可分为两类:

Error(错误):Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError[栈溢出]和OOM(out of memory),Error是严重错误,程序会崩溃。 Exception:其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:空指针访问,试图读取不存在的文件,网络连接中断等等,Exception分为两大类:运行时异常(feishoujian程序运行时发生的异常)和编译时异常(编程时,编译器检查出的异常)。 3.2、异常体系图

常见的异常,体现了继承和实现的关系:

总结:

异常分为两大类:非受检(unchecked)异常(运行时异常) 和 受检(checked)异常(编译时异常)。 运行时异常,编译器检查不出来。一般指编程时的逻辑错误,是程序员应该避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常。 对于运行时异常,可以不做处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。 编译时异常,是编译器要求必须处置的异常,否则代码无法编译通过。 3.3、常见的异常

常见的运行异常:

NullPointerException:空指针异常

当应用程序试图在需要对象的地方使用null时,抛出该异常。

ArithmeticException:数学运算异常

当出现异常的运算条件时,抛出此异常。如:除数为0时,抛出该异常。

ArrayIndexOutOfBoundsException:数组下标越界异常

用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引,抛出索引越界异常

ClassCastException:类型转换异常

当试图将对象强制转换为不是实例的子类时,抛出该异常。

NumberFormatException:数字格式不正确异常

当程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。使用该异常可以确保输入是满足条件数字。

常见的编译异常:

SQLException:操作数据库时,查询表可能发生异常。

IOException:操作文件时,发生的异常。

FileNotFoundException:当操作一个不存在的文件时,发生异常。

ClassNotFoundException:加载类,而该类不存在时,抛出该异常。

EOFException:操作文件,到文件末尾,发生异常。

IllegalArgumentException:参数异常。

3.4、异常处理

异常处理就是当发生异常时,对异常处理的方式。

方式一:使用try-catch-finally:程序员在代码中捕获发生的异常,自行处理。如果没有finally代码块也可以 -> try-catch处理异常。

处理机制示意:

try { 可能有异常的代码块; } catch(Exception e) { 当捕获到异常后,执行catch代码块的内容 1.系统将异常封装成Exception对象e,传递给catch代码块 2.得到异常对象后,程序员自己处理 3.如果没有发生异常,则catch代码块不会执行 } finally { 1.不管代码块是否有异常发生,始终要执行finally代码块的内容 2.通常将释放资源的操作代码放在finally代码块中 }

注意:

如果异常发生了,则异常发生后面的try块中的代码不再执行,直接进入到catch块。 如果异常没有发生,则顺序执行try块,不会进入catch块。 finally块用于执行不管是否发生异常都需要执行的操作。 如果代码块可能有多个异常,可以使用多个catch分别捕获不同的异常进行进行处理。(要求:子类异常在前捕获,父类异常后捕获) 可以进行try-finally配合使用,相当于没有捕获异常,执行完finally块后程序会直接中断。应用场景:执行一段代码,不管是否发生异常,都必须执行某个业务逻辑的代码。 package com.yxz.exception_; public class Exception01 { public static void main(String[] args) { try { int[] arr = {1, 2, 3}; for (int i = 0; i 可以使用默认处理机制)。 public class CustomException { public static void main(String[] args) { int age = 180; if (!(age >= 18 && age 包装类型,反之拆箱。 JDK5及以后都是自动装箱和拆箱方式。 自动装箱底层调用的是valueOf方法,如:Integer.valueOf() public class Integer01 { public static void main(String[] args) { // jdk5前是手动装箱和拆箱 // 手动装箱 int -> Integer int n1 = 100; Integer integer = new Integer(n1); // 手动装箱 Integer integer1 = Integer.valueOf(n1); // 手动装箱 // 手动拆箱 // Integer -> int int i = integer.intValue(); // jdk5之后,自动装箱和自动拆箱 int n2 = 200; // 自动装箱 int -> Integer Integer integer2 = n2; // 底层使用Integer.valueOf(n2) // 自动拆箱 int n3 = integer2; // 底层使用integer.intValue()方法 Object obj1 = true ? new Integer(1) : new Double(2.0); // 三元运算符是一个整体,提升精度 System.out.println(obj1); // 1.0 } }

包装类型和String类型的相互转换

public class Integer01 { public static void main(String[] args) { // 包装类(Integer) -> String Integer i = 100; // 方式1 String str1 = i + ""; // 方式2 String str2 = i.toString(); // 方式3 String str3 = String.valueOf(i); // String -> 包装类(Integer) String str4 = "12345"; Integer i2 = Integer.parseInt(str4);// 自动装箱 Integer i3 = new Integer(str4); // 构造器 } }

包装类的面试题

Object obj1 = true ? new Integer(1) : new Double(2.0); // 三元运算符是一个整体,提升精度 System.out.println(obj1); // 1.0

三元运算符是一个整体,表达式的返回值精度由表达式1和表达式2的最高精度决定。

Integer i = new Integer(1); Integer j = new Integer(1); System.out.println(i == j); // false:使用new创造了对象,则判断对象地址 -> 不同 Integer m = 1; // 自动装箱 底层Integer.valueOf(1) -> 查看valueOf源码 -> 1在[-128, 127]之间,所以不创建对象 Integer n = 1; // 自动装箱 底层Integer.valueOf(1) -> 查看valueOf源码 -> 1在[-128, 127]之间,所以不创建对象 System.out.println(m == n); // true Integer x = 128; // 自动装箱 底层Integer.valueOf(1) -> 查看valueOf源码 -> 128不在[-128, 127]之间,所以创建对象 Integer y = 128; // 自动装箱 底层Integer.valueOf(1) -> 查看valueOf源码 -> 128不在[-128, 127]之间,所以创建对象 System.out.println(x == y); // false

valueOf的源码:

public static Integer valueOf(int i) { if (i >= IntegerCache.low && i value不可以指向新的地址,但是单个字符内容可以变化。

final char[] value = {'a', 'b', 'c'}; value[0] = 'H'; // 可以修改单个字符的值 char[] v2 = {'t', 'o', 'm'}; value = v2; // 错误:不可以更改final类型value的地址

String两种方式创建对象的区别:(⭐⭐⭐⭐⭐)

方式一:直接赋值String s = "yxz";

方式二:调用构造器String s2 = new String("yxz");

说明:

方式一:先从常量池查看是否有"yxz"数据空间,如果有,直接让s指向该数据空间;如果没有则重新创建,然后指向。s最终指向的是常量池的空间地址。

方式二:先在堆中创建空间,里面维护了value属性,指向常量池的"yxz"空间。如果常量池没有"yxz",重新创建,如果有,直接通过value指向。s2最终指向的是堆中的空间地址。

两种方式创建对象的内存布局示意图:

String字符串是不可变的。一个字符串对象一旦被分配,其内容不可变。

面试题:

String a = "hello" + "abc"; 创建了几个对象?一个对象。

String a = "hello" + "abc";编译器底层优化,等价于String a = "helloabc";。

String a = "hello"; String b = "abc"; String c = a + b;

上述代码创建了几个对象?三个字符串常量对象("hello","abc","helloabc"),一个StringBuilder对象,一个String对象

说明:String c = a + b; 底层是Stringbuilder sb = new StringBuilder(); sb.append(a); sb.append(b); String c = sb.toString(); sb是在堆中开辟的对象,并且append()是在原来字符串sb的基础上追加的。

重要规则:String c1 = "ab" + "cd";常量相加,看的是常量池。 String c1 = a + b; 变量相加,底层使用StringBuilder是在堆中new了一个对象。

public class Test { final char[] ch = {'j', 'a', 'v', 'a'}; String str = new String("hsp"); public static void main(String[] args) { Test ex = new Test(); ex.change(ex.str, ex.ch); System.out.print(ex.str + " and "); System.out.println(ex.ch); } public void change(String str, char ch[]) { str = "java"; ch[0] = 'h'; } }

以上代码输出什么?输出:hsp and hava

内存分析图:

String常用方法:

equals():区分大小写,判断内容是否相等 equalsIgnoreCase():忽略大小写的判断内容是否相等 length():获取字符的个数,字符串长度 intern():返回字符串对象内容在常量池的地址。 indexOf():获取字符在字符串中第一次出现的索引,索引从0开始,如果找不到,返回-1 lastIndexOf():获取字符在字符串中最后一次出现的索引,索引从0开始,如找不到,返回-1 substring():截取指定范围的字符串 trim():去掉字符串的前后空格 charAt():获取某索引处的字符,注:不能使用str[index]的方式获取 toUpperCase():将字符串中所有字母都大写并返回。 toLowerCase():将字符串中所有字母都小写并返回。 concat():拼接字符串并返回 replace():替换字符串中的字符 split():分割字符串 compareTo():比较字符串大小 toCharArray():转换成字符数组 format():格式字符串 4.3、StringBuffer

java.lang.StringBuffer代表可变的字符序列,可以对字符串内容进行增删。

很多方法和String相同,但StringBuffer是可变长度的。

StringBuffer是一个容器。

StringBuffer的继承关系图:

直接父类AbstractStringBuilder中有属性char[] value,不是final类型,该value数组存放StringBuffer对象的字符串内容,不是final -> 可以修改字符串内容 -> value数组存放在堆中。

StringBuffer实现了Serializable,即StringBuffer的对象可以串行化。

StringBuffer是final类,不能被继承。

StringBuffer保存的是字符串变量,里面的值可以更改,每次StringBuffer的更新实际上可以更新内容,不用每次更新地址(创建新对象),效率较高。

String每次更新都会创建新的对象(如果常量池中有字符串则不会创建)并更改地址。

StringBuffer创建对象时,默认给value数组设置大小为16,可以调用构造器显式指定其StringBuffer的大小 = str.length() + 16。

String -> StringBuffer 的两种方式

方式一:调用有参构造器StringBuffer(String str)

String str = "hello"; StringBuffer sb1 = new StringBuffer(str);

方式二:调用append(String str)方法

StringBuffer sb2 = new StringBuffer(); sb2 = sb1.append(str);

StringBuffer -> String 的两种方式

方式一:toString()方法

StringBuffer sb3 = new StringBuffer("hello"); String s = sb3.toString();

方式二:使用String的构造器传入StringBuffer对象

String s1 = new String(sb3);

StringBuffer类常用方法

append():增加字符或字符串 delete(int start, int end):删除索引[start, end)范围内的字符。 replace(int start, int end, String str):替换索引[start, end)内的内容为字符(串)str indexOf():查找指定的子串在字符串中第一次出现的索引 insert():在指定索引处插入字符(串) length():返回字符串长度。 4.4、StringBuilder 一个可变的字符序列。此类提供一个与StringBuffer兼容的API(两者方法一样),但不保证同步(StringBuilder不是线程安全的)。该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候。在单线程中,建议优先使用该类,大部分情况下它比StringBuffer更快。 在StringBuilder上的主要操作是append和insert方法,可重载这些方法,以接收任意类型的数据。 StringBuilder继承AbstractSrtingBuilder类,实现了Serializable接口 -> StringBuilder对象可以串行化(可以网络传输、保存到文件)。 StringBuilder是final类,不能被继承。 StringBuilder的方法,没有做互斥的处理,即没有synchronized关键字 -> 因此推荐在单线程情况下使用StringBuilder。

String、StringBuffer、StringBuilder的比较

StringBuilder和StringBuffer非常类似,均代表可变的字符序列,而且方法也一样。 String:不可变字符序列,效率低,但是复用率高 -> 如果要对字符串做大量修改操作,不要使用String StringBuffer:可变字符序列,效率较高(增删),线程安全。 StringBuilder:可变字符序列,效率最高,线程不安全。

String、StringBuffer、StringBuilder的选择

如果字符串存在大量的修改操作,一般使用StringBuffer或StringBuilder。 如果字符串存在大量的修改操作,并在单线程的情况,使用StringBuilder。 如果字符串存在大量的修改操作,并在多线程的情况,使用StringBuffer。 如果字符串很少修改,被多个对象引用,使用String,如配置信息等。 4.5、Math

Math类常用方法:

abs():绝对值 pow(double a, double b):求a的b次幂 ceil(double a):向上取整,返回大于等于a的最小整数 floor(double a):向下取整,返回大于等于a的最大整数 round(double/float a):四舍五入。先给a+0.5,再取整返回一个整型(long/int) sqrt():求开方 random():生成随机数。返回0 文本)、解析(文本 -> 日期)和规范化。

创建SimpleDateFormat对象,可以指定相应的格式。格式中使用的字母是规定好的,不能乱写,格式字符规定:

SimpleDateFormat类构造器:

SimpleDateFormat(String pattern):pattern是根据规定格式字母组成的字符串

如:SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");

SimpleDateFormat类的常用方法:

format(Date date):将指定日期转换成指定格式的字符串 parse(String s):可以把一个格式化的String 转成对应的Date,得到的Date在输出时仍然是国外的方式,如果希望指定格式输出,需要format转换。在把String转成Date时,使用的SimpleDateFormat格式需要和String的格式一样,否则会抛出转换异常ParseException。

示例代码:

import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class UseDate { public static void main(String[] args) throws ParseException { // 抛出异常 // 获取当前系统时间 Date date = new Date(); // 默认输出的日期格式是国外的方式,因此通常需要对格式进行转换 System.out.println("当前时间是:" + date); Date date1 = new Date(92733); // 通过指定毫秒数得到时间 System.out.println(date.getTime()); // 获取某个时间对应的毫秒数 // 创建SimpleDateFormat对象,根据字母规定,传入格式串 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E"); String format = simpleDateFormat.format(date); // 将日期转换成指定格式的字符串 System.out.println(format); String s = "2022年11月07日 05:20:00 周一"; Date parseDate = simpleDateFormat.parse(s); // 可能会抛出转换异常,需要抛出异常或者try-catch捕获异常 System.out.println("转换后的日期:" + simpleDateFormat.format(parseDate)); } }

第二代日期类Calendar(在java.util包下)

public abstract class Calendar implements Serializable, Cloneable, Comparable {} Calendar类是一个抽象类,它为特定瞬间与一组诸如YEAR、MONTH、DAY_OF_MONTH、HOUR等日历、字段之间的转换提供了一些方法,并为操作日历提供了方法。 Calendar类的构造器是受保护的,获取其对象可以通过getInstance()方法。 Calendar类提供大量的方法和字段给程序员,通过获取字段转化成时间。 Calendar没有提供对应的格式化的类,需要程序员自己组合。 如果要获取24时进制的小时数,将Calendar.HOUR -> Calendar.HOUR_OF_DAY import java.util.Calendar; public class Calendar_ { public static void main(String[] args) { Calendar c = Calendar.getInstance(); System.out.println(c); System.out.println("年:" + c.get(Calendar.YEAR)); // Calendar返回月份是从0月开始编号 System.out.println("月:" + (c.get(Calendar.MONTH) + 1)); System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH)); System.out.println("小时:" + c.get(Calendar.HOUR)); System.out.println("分钟:" + c.get(Calendar.MINUTE)); System.out.println("秒:" + c.get(Calendar.SECOND)); // 自己组合显示日期时间 System.out.println(c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1) + "-" + c.get(Calendar.DAY_OF_MONTH) + " " + c.get(Calendar.HOUR) + ":" + c.get(Calendar.MINUTE) + ":" + c.get(Calendar.SECOND)); } }

第三代日期类

JDK 1.0中包含了java.util.Date类,但是它的大多数方法在JDK 1.1引入Calendar类后就被弃用。而Calendar类也存在问题:

可变性:像日期和时间这样的类应该是不可变的 偏移性:Date中的年份是从1900年开始的,而月份都是从0开始 格式化:格式化只对Date有用,Calendar没有格式化类 Date类和Calendar类不是线程安全的,不能处理闰秒等(每隔2年,多出1秒)

为了解决以上问题,JDK 8.0引入第三代日期类

第三代日期类:

LocalDate类(日期/年月日),在java.time包下 LocalTime类(时间/时分秒),在java.time包下 LocalDateTime类(日期时间/年月日时分秒),在java.time包下

LocalDateTime类常用方法:

now():静态方法,返货一个LocalDateTime类的对象

getYear():年

getMonth():英语月份

getMonthValue():数字月份

getDayOfMonth():日

getHour():小时(24进制)

getMinute():分钟

getSecond():秒

plusDays(long days):当前时间加上一个天数,返回一个将来的时间

plusYears(long years):当前时间加上年数,返回将来的时间。

minusWeeks(long weeks):当前时间减去周数,返回过去的时间

minusHours(long hours):当前时间减去小时数,返回过去的时间

等等,需要什么方法,查阅JDK 8.0版本之后的API文档

在开发中,尽量使用第三代日期类。

DateTimeFormatter格式日期类(在java.time.format包下)

类似于SimpleDateFormat类

DateTimeFormatter dtf = DateTimeFormatter.ofPattern(格式字符串);

String format = dtf.format(日期对象);

import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; public class UseLocalDateTime { public static void main(String[] args) { LocalDateTime ldt = LocalDateTime.now(); System.out.println(ldt); System.out.println("年:" + ldt.getYear()); System.out.println("月:" + ldt.getMonth()); // 英语月份 System.out.println("月:" + ldt.getMonthValue()); // 数字月份 System.out.println("日:" + ldt.getDayOfMonth()); System.out.println("时:" + ldt.getHour()); System.out.println("分:" + ldt.getMinute()); System.out.println("秒:" + ldt.getSecond()); LocalDate now = LocalDate.now(); // 可以获取年月日 LocalTime now1 = LocalTime.now(); // 可以获取时分秒 // 使用DateTimeFormatter对象 对 LocalDateTime对象进行格式化 DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH小时mm分ss秒"); String format = dateTimeFormatter.format(ldt); System.out.println("格式化的日期 = " + format); LocalDateTime localDateTime = ldt.plusDays(999); System.out.println("999天后是:" + dateTimeFormatter.format(localDateTime)); } }

第三代日期类Instant(在java.time包下)

通过Instant类的静态方法now()获取表示当前时间戳的对象。 通过Date类的form()方法,可以把Instant转成Date对象。 通过Date对象的toInstant()方法可以把date转成Instant对象。 import java.time.Instant; import java.util.Date; public class UseInstant { public static void main(String[] args) { Instant now2 = Instant.now(); System.out.println(now2); Date from = Date.from(now2); Instant instant = from.toInstant(); } } 4.7、System

System类的常见方法:

exit():退出当前程序。exit(0)表示程序正常退出,0-正常状态。

arraycopy(Type[] src, int srcPos, Type[] des, int destPos, int length):复制数组元素,比较适合底层调用,一般使用Arrays.copyOf完成复制数组。参数说明:src源数组,srcPos从源数组的哪个索引位置开始拷贝,des目标数组,destPos把源数组src的数据拷贝到目标数组dest的哪个索引,length从源数组拷贝多少个数据到目标数组dest

int[] src = {1, 2, 3}; int[] dest = new int[3]; System.arraycopy(src, 0, dest, 0, src.length);

currentTimeMillis():返回当前时间距离1970年1月1日0时0分的毫秒数(返回值long型)

gc():运行垃圾回收机制System.gc();

4.8、Arrays

Arrays里面包含一系列静态方法,用于管理和操作数组。

Arrays类常用方法:

toString():返回数组的字符串形式。如:Arrrays.toString(arr)

sort():排序(自然排序和定制排序)

示例代码:

import java.util.Arrays; import java.util.Comparator; public class SortDetail { public static void main(String[] args) { Integer[] arr = {7, -1, 89, 53, 66}; /* 1.sort方法是重载的,可以通过传入一个接口Comparator实现定制排序 2.调用定制排序时,传入两个参数 (1)待排序的数组arr (2)实现了Comparator接口的匿名内部类,要实现接口的compare()方法 sort方法会调用TimSort类的private static void binarySort(T[] a, int lo, int hi, int start, Comparator caller) { // This can be null if the VM is requesting it if (caller == null) { return null; } // Circumvent security check since this is package-private return caller.getClassLoader0(); } ClassLoader getClassLoader0() { return classLoader; } // 系统调用类加载器创建Class类对象 public Class loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } */ // 3. 对于某个类的Class类对象, 在内存中只有一个, 因为类只加载一次 Class cls2 = Class.forName("com.lww.reflection.Cat"); System.out.println(cls1.hashCode()); System.out.println(cls2.hashCode()); // cls1 == cls2 同一个类对象 // 4.每个类的实例都会记得自己是由哪个Class类实例所生成 } }

Class类常用方法:

方法名 功能说明 static Class forName(String name) 返回指定类名name的Class对象 Object newInstance() 调用缺省构造函数, 返回该Class对象的一个实例 getName() 返回此Class对象所表示的实体(类, 接口, 数组类, 基本类型等)名称 Class[] getInterfaces() 获取当前Class类对象的接口 ClassLoader getClassLoader() 返回该类的类加载器 Class getSuperclass() 返回表示此Class所表示的实体的超类Class Constructor[] getConstructors() 返回一个包含某些Constructor对象的数组 Field[] getDeclaredFields() 返回Field对象的一个数组 Method getMethod(String name, Class... parameterTypes) 返回一个Method对象, 此对象的形参类型为parameterTypes package com.lww.reflection; public class Car { public String brand = "宝马"; public int price = 500000; public String color = "渐变蓝色"; @Override public String toString() { return "Car{" + "brand='" + brand + '\'' + ", price=" + price + ", color='" + color + '\'' + '}'; } } package com.lww.reflection; import java.lang.reflect.Field; // 演示Class类的常用方法 @SuppressWarnings({"all"}) public class Class02 { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException { String classAllPath = "com.lww.reflection.Car"; // 获取Car类对应的Class对象 // 表示不确定的Java类型 Class cls = Class.forName(classAllPath); System.out.println(cls); // 显示cls对象, 是哪个类的Class对象 com.lww.reflection.Car System.out.println(cls.getClass()); // 输出cls的运行类型 java.lang.Class // 得到包名 System.out.println(cls.getPackage().getName()); // com.lww.reflection // 得到全类名 System.out.println(cls.getName()); // 通过cls创建对象实例 Car car = (Car) cls.newInstance(); System.out.println(car); // 调用car的toString()方法 // 通过反射获取属性 brand Field brand = cls.getField("brand"); // 只能获取公共属性 System.out.println(brand.get(car)); // 宝马 // 通过反射给属性赋值 brand.set(car, "奥迪"); System.out.println(brand.get(car)); // 奥迪 // 通过Field[]对象获取所有字段属性 Field[] fields = cls.getFields(); for (Field field : fields) { System.out.print(field.get(car) + " "); } } }

获取Class类对象

前提: 已知一个类的全类名, 且该类在类路径下, 可以通过Class类的静态方法forName()获取, 可能抛出ClassNotFoundException.

如: Class cls1 = Class.forName("com.lww.reflection.Car");

应用场景: 多用于配置文件, 读取类的全路径, 加载类

前提: 若已知具体的类, 通过类的class获取, 该方式最安全可靠, 程序性能最高.

如: Class cls2 = Cat.class;

应用场景: 多用于参数传递, 比如通过反射得到对应构造器对象

前提: 已知某个类的实例obj, 调用该实例的getClass()方法获取Class对象

如:Class cls3 = obj.getClass(); 获取它的运行类型

应用场景: 有对象实例, 通过创建好的对象实例obj, 获取Class对象

ClassLoader cl = obj.getClass().getClassLoader();

Class cls4 = cl.loadClass("类的全类名");

基本数据类型(int, char, boolean, float, double, byte, long, short)都有Class对象. 通过基本数据类型.class获取Class对象

Class integerClass = int.class; Class characterClass = char.class; Class booleanClass = boolean.class; Class floatClass = float.class; Class doubleClass = double.class; Class byteClass = byte.class; Class shortClass = short.class; Class longClass = long.class;

基本数据类型对应的包装类, 可以通过包装类.TYPE得到Class对象

Class type1 = Integer.TYPE; Class type2 = Character.TYPE; Class type3 = Boolean.TYPE; Class type4 = Float.TYPE; Class type5 = Double.TYPE; Class type6 = Byte.TYPE; Class type7 = Short.TYPE; Class type8 = Long.TYPE; package com.lww.reflection; @SuppressWarnings({"all"}) public class GetClass_ { public static void main(String[] args) throws ClassNotFoundException { // 1.Class.forName() String classAllPath = "com.lww.reflection.Car"; // classAllPath一般通过配置文件读取 Class cls1 = Class.forName(classAllPath); System.out.println(cls1); // 2.类名.class Class cls2 = Car.class; // 需要在同一个包下 System.out.println(cls2); // 3.对象.getClass() Car car = new Car(); Class cls3 = car.getClass(); System.out.println(cls3); // 4.通过类加载器[4种]来获取类的Class对象 // (1)先得到类加载器car ClassLoader classLoader = car.getClass().getClassLoader(); // (2)通过类加载器得到Class对象 Class cls4 = classLoader.loadClass(classAllPath); System.out.println(cls4); // 对于同一个类而言, 上述四种方式获取的Class对象是同一个对象 System.out.println(cls1.hashCode() + ", " + cls2.hashCode() + ", " + cls3.hashCode() + ", " + cls4.hashCode()); // 5.基本数据类型(int, char, boolean, float, double, byte, long, short)都有Class对象 Class integerClass = int.class; Class characterClass = char.class; Class booleanClass = boolean.class; Class floatClass = float.class; Class doubleClass = double.class; Class byteClass = byte.class; Class shortClass = short.class; Class longClass = long.class; System.out.println(integerClass); // int, 自动装箱和自动拆箱 // 6.基本数据类型对应的包装类, 可以通过包装类.TYPE 得到Class对象 Class type1 = Integer.TYPE; Class type2 = Character.TYPE; Class type3 = Boolean.TYPE; Class type4 = Float.TYPE; Class type5 = Double.TYPE; Class type6 = Byte.TYPE; Class type7 = Short.TYPE; Class type8 = Long.TYPE; System.out.println(type3); // boolean System.out.println(type1 == integerClass); // true: 同一个对象, Java底层对于基本数据类型会进行自动装箱/拆箱 } }

以下类型都有Class对象:

外部类, 成员内部类, 静态内部类, 局部内部类, 匿名内部类 interface:接口 数组 enum: 枚举 annotation: 注解 基本数据类型, 包装类 void Class类 package com.lww.reflection; import java.io.Serializable; public class AllTypeClass { public static void main(String[] args) { Class cls1 = String.class; // 外部类 Class cls2 = Serializable.class; // 接口 Class cls3 = Integer[].class; // 数组 Class cls4 = double[][].class; // 二维数组 Class cls5 = Deprecated.class; // 注解 Class cls6 = Thread.State.class; // 枚举 Class cls7 = long.class; // 基本数据类型 Class cls8 = Float.class; // 包装类 Class cls9 = void.class; // void数据类型 Class cls10 = Class.class; // Class -> 万物皆对象! } } 2.3、类的加载

基本说明: 反射机制是Java实现动态语言的关键, 也就是通过反射实现类动态加载

静态加载: 编译时加载相关的类, 如果没有对应类则报错, 依赖性强. new创建对象时在编译阶段会静态加载类信息 动态加载: 运行时加载需要的类, 如果运行时不用该类, 即使不存在该类也不会报错, 降低了依赖性. 反射是动态加载, 只有运行到相关类的代码时才会加载信息

类加载时机:

当使用new创建对象时(静态加载) 当子类被加载时, 父类也会被加载(静态加载) 调用类中的静态成员时(静态加载) 通过反射(动态加载)

类加载过程图(⭐⭐⭐⭐⭐)

各阶段完成的任务(⭐⭐⭐⭐⭐)

类加载的五个阶段:(⭐⭐⭐⭐⭐)

加载阶段:JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件, 也可能是jar包, 甚至网络)转化为二进制字节流加载到内存中, 并生成一个代表该类的java.lang.Class对象.

连接阶段-验证:

目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求, 并且不会危害虚拟机自身的安全 包括: 文件格式验证(是否以魔数oxcafebabe开头, 打开class文件, 内容以oxcafebabe开头), 元数据验证, 字节码验证和符号引用验证 可以考虑使用 -Xverify:none参数来关闭大部分的类验证措施, 缩短虚拟机类加载的时间

连接阶段-准备:

JVM会在该阶段对静态变量分配内存并默认初始化(对应数据类型的默认初始值, 如0, 0L, null, false等). 这些变量所使用的内存都将在方法区中(也有说法在堆中)进行分配. 非静态变量在该阶段不会分配内存. 静态常量会根据指定赋值初始化

class A { // 连接阶段-准备, 属性如何处理: // n1是实例属性, 不是静态变量, 因此在该阶段不会为n1分配内存 public int n1 = 10; // n2是static, 是静态变量, 分配内存, 默认初始化n2 = 0, 不是20 public static int n2 = 20; // n3是static final, 是常量, 和静态变量不一样, 因为类常量一旦赋值就不变, 因此直接初始化赋值n3 = 30 public static final int n3 = 30; }

连接阶段-解析:

虚拟机将常量池内的符号引用替换为直接引用(内存地址)的过程.

Initialization初始化

到初始化阶段, 才真正开始执行类中定义的Java程序代码, 此阶段是执行()方法的过程.

()方法是由编译器按语句在源文件中出现的顺序, 依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句, 并进行合并.

虚拟机会保证一个类的()方法在多线程环境中被正确地加锁-同步, 如果多个线程同时去初始化一个类, 那么只会有一个线程去执行这个类的()方法, 其他线程都需要阻塞等待, 直到活动线程执行()方法完毕. 正因为有这个线程同步机制, 才能保证某个类在内存中只有一个Class对象

底层源码

protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { // 正因为有这个线程同步机制, 才能保证某个类在内存中只有一个Class对象 synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } } 2.4、反射获取类的结构信息

反射获取类的结构信息的常用方法

java.lang.Class类提供获取类的结构信息的方法:

方法名 作用 getName 获取全类名(包名.类名) getSimpleName 获取类名 getFields 获取所有public修饰的属性, 包含本类以及父类(直接父类和间接父类)的 getDeclaredFields 获取本类中所有的属性 getMethods 获取所有public修饰的方法, 包括本类以及父类(直接父类和间接父类)的 getDeclaredMethods 获取本类中所有的方法 getConstructors 获取本类所有public修饰的构造器 getDeclaredConstructors 获取本类所有的构造器 getPackage 以Package形式返回包信息 getSuperClass 以Class形式返回父类信息 getInterfaces 以Class[]形式返回所有接口信息 getAnnotations 以Annotation[]形式返回所有注解信息

java.lang.reflect.Field类提供获取类的字段信息的方法:

方法名 作用 getModifiers 以int形式返回修饰符.[0-默认修饰符,1-public,2-private,4-protected,8-static,16-final] 如:public static = 1 + 8 = 9 getType 以Class形式返回字段的类型 getName 返回字段名

java.lang.reflect.Method类提供获取类的方法信息的方法:

方法名 作用 getModifiers 以int形式返回修饰符.[0-默认修饰符,1-public,2-private,4-protected,8-static,16-final] 如:protected static = 4 + 8 = 12 getReturnType 以Class形式获取返回类型 getName 返回方法名 getParameterTypes 以Class[]返回参数类型数组

java.lang.reflect.Constructor类提供获取类的构造器信息的方法:

方法名 作用 getModifiers 以int形式返回修饰符.[0-默认修饰符,1-public,2-private,4-protected] getParameterTypes 以Class[]返回参数类型数组 getName 返回构造器名(全类名)

反射**

通过反射创建对象的两种方式:

调用类中的public修饰的默认无参构造器 调用类中的指定构造器

Class类相关方法:

newInstance():调用类中的无参构造器, 获取对应类的对象 getConstructor(Class...clazz): 根据参数列表, 获取对应的public构造器对象 getDecalaredConstructor(Class...clazz):根据参数列表, 获取对应的构造器对象

Constructor类相关方法:

setAccessible(boolean flag):暴破. 使用反射解决访问非public构造器的问题 newInstance(Object...obj):调用构造器 package com.lww.reflection; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class ReflectCreateInstance { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { // 1.先获取到User类的Class对象 Class userClass = Class.forName("com.lww.reflection.User"); // 2.通过public的无参构造器创建实例 Object o = userClass.newInstance(); System.out.println(o); // 3.通过public的有参构造器创建实例 // 3.1.得到对应的有参构造器 Constructor constructor = userClass.getConstructor(String.class); // 3.2.创建对象实例并传入实参 Object o1 = constructor.newInstance("yxz"); System.out.println(o1); // 4.通过非public的有参构造器创建实例 // 4.1.得到private的构造器对象 Constructor constructor1 = userClass.getDeclaredConstructor(String.class, int.class); // 4.2 创建对象实例 constructor1.setAccessible(true); // 暴破(暴力**): 使用反射可以访问非public构造器 Object yxz = constructor1.newInstance("yxz", 21); System.out.println(yxz); } } class User { private String name; private int age; private User(String name, int age) { this.name = name; this.age = age; } public User(String name) { this.name = name; } public User() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }

通过反射访问类中的成员

访问属性

根据属性名获取Field对象 暴破: fieldObject.setAccessible(true): fieldObject是字段对象. 参数设置为true可以访问类中的非public字段 访问: fieldObject.set(object, 值): object表示对象名, 给object对象设置字段属性值 syso(fieldObject.get(object)): object是对象名 如果是静态属性, 则set和get中的参数object可以写成null package com.lww.reflection; import java.lang.reflect.Field; public class ReflectAccessProperty { public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchFieldException { // 1.得到Student类对应的Class对象 Class studentClass = Student.class; // 2.创建对象 Object o = studentClass.newInstance(); // 3.使用反射得到age属性对象 Field age = studentClass.getField("age"); age.set(o, 23); System.out.println(o); System.out.println(age.get(o)); // 4.使用反射操作静态私有属性name Field name = studentClass.getDeclaredField("name"); // 对name进行暴破, 访问私有属性 name.setAccessible(true); name.set(o, "yxz"); name.set(null, "lww"); // 只有静态属性的对象可以为null, 静态属性与对象无关 System.out.println(o); System.out.println(name.get(o)); System.out.println(name.get(null)); // 静态属性的对象可以为null } } class Student { private static String name; public int age; public Student() { } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }

访问方法

根据方法名和参数列表获取Method方法对象:Method m = clazz.getDeclaredMethod(方法名, XX.class); 获取对象: Object o = clazz.newInstance(); 暴破: m.setAccessible(true); 访问: Object returnValue = m.invoke(o, 方法的实参列表); 如果是静态方法, 则invoke的参数o可以写成null 在反射中, 如果方法有返回值, 统一返回(编译类型)Object对象, 该对象运行类型与方法返回值类型一致 package com.lww.reflection; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ReflectAccessMethod { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { // 1.加载类 Class bossCls = Class.forName("com.lww.reflection.Boss"); // 2.创建对象实例 Object o = bossCls.newInstance(); // 3.获取方法对象 // 3.1 得到hi方法对象 Method hi = bossCls.getMethod("hi", String.class); // 传入方法的形参class对象 Method hi1 = bossCls.getDeclaredMethod("hi", String.class); // 3.2 调用 hi.invoke(o, "java"); // 4.调用private static方法 // 4.1 得到方法对象 Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class); // 4.2 因为say方法是private, 所以需要暴破 say.setAccessible(true); // 4.3 调用方法 System.out.println(say.invoke(o, 21, "lww", '女')); // 因为say方法为静态方法, 所以对象可以为null System.out.println(say.invoke(null, 22, "yxz", '男')); // 在反射中, 如果方法有返回值, 统一返回Object对象 Object invoke = say.invoke(null, 21, "lww", '女'); System.out.println(invoke.getClass()); // class java.lang.String } } class Boss { private static String name; public int age; public Boss() { } private static String say(int n, String s, char c) { return n + " " + s + " " + c; } public void hi(String s) { System.out.println("hi " + s); } @Override public String toString() { return "Boss{" + "name='" + name + '\'' + ", age=" + age + '}'; } }

反射综合练习:使用反射机制创建文件

package com.lww.reflection; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ReflectExercise { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { // 1. 加载File类 Class fileCls = Class.forName("java.io.File"); // 2. 获取所有构造器 Constructor[] declaredConstructors = fileCls.getDeclaredConstructors(); for (Constructor declaredConstructor : declaredConstructors) { System.out.println("构造器: " + declaredConstructor); } // 3. 指定得到File(java.lang.String)构造器 Constructor declaredConstructor = fileCls.getDeclaredConstructor(String.class); // 4. 指定文件路径 String fileAllPath = "d:\\Java\\Demo\\reflect.txt"; // 5. 创建File对象 Object o = declaredConstructor.newInstance(fileAllPath);// 创建File对象, 此时内存中只有对象, 还未创建文件 // 6. 获取createNewFile()方法的对象 Method createNewFile = fileCls.getDeclaredMethod("createNewFile"); // 7. 调用方法获取createNewFile()创建文件 createNewFile.invoke(o); // ==> o.createNewFile() System.out.println("文件创建成功"); } } 3、Mysql基础

此章节笔记见: [MySQL基础](D:\Program Files (x86)\MarkDown For Typora\网课笔记\MySQL基础)

4、JDBC和连接池 4.1、JDBC概述

基本介绍:

JDBC为访问不同的数据库提供了一组接口,为使用者屏蔽了细节问题。

Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。

JDBC基本原理:

模拟JDBC:

JdbcInterface.java文件 -> 模拟Java厂商指定的规范

package com.jdbc.imitate; public interface JdbcInterface { // 获取数据库连接 public Object getConnection(); // 模拟CRUD public void crud(); // 关闭数据库连接 public void close(); }

MysqlJdbcImpl.java文件 -> 模拟MySQL厂商的实现接口类(jar包,驱动程序)

package com.jdbc.imitate; public class MysqlJdbcImpl implements JdbcInterface { @Override public Object getConnection() { System.out.println("模拟 获取到MySQL的连接..."); return null; } @Override public void crud() { System.out.println("模拟 MySQL中增删改查..."); } @Override public void close() { System.out.println("模拟 MySQL的关闭连接..."); } }

OracleJdbcImpl.java文件 -> 模拟Oracle厂商的实现接口类(jar包,驱动程序)

package com.jdbc.imitate; public class OracleJdbcImpl implements JdbcInterface { @Override public Object getConnection() { System.out.println("模拟 获取到Oracle的连接..."); return null; } @Override public void crud() { System.out.println("模拟 Oracle中增删改查..."); } @Override public void close() { System.out.println("模拟 Oracle的关闭连接..."); } }

JdbcTest.java测试类 -> 模拟Java程序操作各种数据库

package com.jdbc.imitate; public class JdbcTest { public static void main(String[] args) { JdbcInterface jdbcInterface1 = new MysqlJdbcImpl(); jdbcInterface1.getConnection(); jdbcInterface1.crud(); jdbcInterface1.close(); System.out.println("=============================="); JdbcInterface jdbcInterface2 = new OracleJdbcImpl(); jdbcInterface2.getConnection(); jdbcInterface2.crud(); jdbcInterface2.close(); } } 4.2、JDBC快速入门

JDBC API介绍:

JDBC API是一系列的接口,它统一和规范了应用程序和数据库的连接、执行SQL语句,并得到返回结果等各类操作,相关类和各类接口在java.sql包和javax.sql包中。

JDBC程序的编写步骤:

准备工作:下载相应驱动包(jar包)并添加到项目目录下的libs(自己创建的)目录,右键点击“add as library”加入项目库。 注册驱动 -> 加载Driver类 获取连接 -> 得到Connection(数据库的连接) 执行增删改查 -> 发送SQL命令给相应数据库系统执行 释放资源 -> 关闭相关数据库连接

数据库连接方式1:通过new com.mysql.cj.jdbc.Driver()获取Driver对象,这种方式属于静态加载,灵活性差,依赖性强

package com.jdbc.myjdbc; import com.mysql.cj.jdbc.Driver; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; public class Jdbc01 { public static void main(String[] args) throws SQLException { // 0.前置工作:在项目下创建一个libs目录,用于放各种jar包, // 如mysql的驱动包,并右键jar包 点击"add as library" 加入到项目库 // 1.注册驱动 Driver driver = new Driver(); /* 说明: jdbc:mysql:// -> 固定格式,表示协议,通过jdbc的方式连接mysql localhost -> 表示主机,可以是ip地址(localhost表示的ip地址127.0.0.1) 3380 -> 表示mysql服务监听的端口号 jdbc_test -> 表示mysql DBMS连接的哪一个数据库 mysql连接的本质就是socket连接 */ String url = "jdbc:mysql://localhost:3380/jdbc_test"; // 将用户名和密码放入到Properties对象中 // 说明:user 和 password是规定好的key,后面的值对应用户名和密码 Properties properties = new Properties(); properties.setProperty("user", "root"); properties.setProperty("password", "123456"); // 2.得到连接 Connection connect = driver.connect(url, properties); // 3.执行sql语句 String sql = "INSERT INTO goddess(NAME,borndate) VALUES('刘亦菲','1987-08-25')"; // Statement对象 用于执行静态 SQL 语句并返回它所生成结果的对象。 Statement statement = connect.createStatement(); // 返回受DML操作影响的行数 int resultRows = statement.executeUpdate(sql); System.out.println(resultRows > 0 ? "插入成功" : "操作失败"); // 4.关闭资源 statement.close(); connect.close(); } }

数据库连接方式2:通过反射Class.forName()方式,动态加载Driver类,更加灵活,减少依赖性

数据库连接方式3:使用DriverManager替代Driver

数据库连接方式4:使用Class.forName()自动完成注册驱动,简化代码

注:MySQL驱动5.1.6及以后的版本可以无需Class.forName(“com.mysql.cj.jdbc.Driver”)

从JDK1.5以后使用了JDBC4,不再需要显式调用Class.forName()注册驱动,而是自动调用驱动包jar下的路径:META-INF/services/java.sql.Driver文本中的类名称去注册。

建议:写上Class.forName("com.mysql.cj.jdbc.Driver"),可读性更好

数据库连接方式5:在方式4的基础上,使用配置文件,连接数据库更加灵活

参考代码:

package com.jdbc.myjdbc; import com.mysql.cj.jdbc.Driver; import org.junit.jupiter.api.Test; import java.io.FileInputStream; import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; public class Jdbc02 { // 方式1:new com.mysql.cj.jdbc.Driver()静态获取Driver对象 @Test public void connect01() throws SQLException { Driver driver = new Driver(); String url = "jdbc:mysql://localhost:3380/jdbc_test"; Properties properties = new Properties(); properties.setProperty("user", "root"); properties.setProperty("password", "123456"); Connection connect = driver.connect(url, properties); System.out.println("方式2:" + connect); } // 方式2:通过反射,动态加载Driver @Test public void connect02() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException { Class aClass = Class.forName("com.mysql.cj.jdbc.Driver"); Driver driver = (Driver) aClass.newInstance(); String url = "jdbc:mysql://localhost:3380/jdbc_test"; Properties properties = new Properties(); properties.setProperty("user", "root"); properties.setProperty("password", "123456"); Connection connect = driver.connect(url, properties); System.out.println("方式2:" + connect); } // 方式3:使用DriverManager替代Driver @Test public void connect03() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException { Class aClass = Class.forName("com.mysql.cj.jdbc.Driver"); Driver driver = (Driver) aClass.newInstance(); String url = "jdbc:mysql://localhost:3380/jdbc_test"; String user = "root"; String password = "123456"; Properties properties = new Properties(); properties.setProperty("user", user); properties.setProperty("password", password); // 向 DriverManager 注册给定驱动程序。 DriverManager.registerDriver(driver); Connection connection = DriverManager.getConnection(url, properties); System.out.println("方式3:" + connection); } // 方式4:使用Class.forName()自动完成注册驱动,简化代码(实际开发中使用最多,推荐) @Test public void connect04() throws ClassNotFoundException, SQLException { // 在加载Driver类时,自动完成了注册驱动 /* 源码分析: public class Driver extends NonRegisteringDriver implements java.sql.Driver { public Driver() throws SQLException { } // 1.静态代码块:会在类加载时执行一次 // 2.DriverManager.registerDriver(new Driver()); 注册驱动 // 3.因此注册驱动已经完成 static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } } } */ Class.forName("com.mysql.cj.jdbc.Driver"); String url = "jdbc:mysql://localhost:3380/jdbc_test"; String user = "root"; String password = "123456"; Connection connection = DriverManager.getConnection(url, user, password); System.out.println("方式4:" + connection); } // 方式5:在方式4的基础上,使用配置文件,连接数据库更加灵活 @Test public void connect05() throws IOException, ClassNotFoundException, SQLException { // 通过Properties对象获取配置文件的信息 Properties properties = new Properties(); properties.load(new FileInputStream("src\\mysql.properties")); // 获取相关的值 String user = properties.getProperty("user"); String password = properties.getProperty("password"); String driver = properties.getProperty("driver"); String url = properties.getProperty("url"); Class.forName(driver); // 建议写上 Connection connection = DriverManager.getConnection(url, user, password); System.out.println("方式5:" + connection); } }

mysql.properties配置文件

user=root password=123456 url=jdbc:mysql://localhost:3380/jdbc_test driver=com.mysql.cj.jdbc.Driver

ResultSet结果集

基本介绍:

表示数据库结果集的数据表,通常通过执行查询数据库的语句生成。 ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前。 next()方法将光标移动到下一行,并且由于ResultSet对象中没有更多行时返回false,因此可以在while循环中使用循环来遍历结果集。 getXxx(int index|String columnName)方法:参数可以为列的索引(即第几列,从1开始),也可以是列名(不受select语句中列名顺序影响)。获取某一行某一列的值。 package com.jdbc.resultset; import java.io.FileInputStream; import java.io.IOException; import java.sql.*; import java.util.Properties; public class ResultSetTest { public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException { Properties properties = new Properties(); // Properties.load()中加载Properties文件的路径是绝对路径 properties.load(new FileInputStream("D:\\Program Files (x86)\\IDEA\\Projects\\Module02_Study\\src\\mysql.properties")); // getResourceAsStream(String path): // 如果path带"/",那么就是从类路径.classpath中去找文件, // 如果path不带"/",那么就是从当前class文件的路径下找文件 // properties.load(ResultSetTest.class.getResourceAsStream("/mysql.properties")); // 获取相关值 String user = properties.getProperty("user"); String password = properties.getProperty("password"); String driver = properties.getProperty("driver"); String url = properties.getProperty("url"); // 1. 注册驱动 Class.forName(driver); // 2. 得到连接 Connection connection = DriverManager.getConnection(url, user, password); // 3. 得到Statement Statement statement = connection.createStatement(); // 4. 编写sql语句 String sql = "select id,name,sex,borndate from goddess"; // executeQuery(String sql): 执行给定的 SQL 语句,该语句返回单个 ResultSet 对象。 ResultSet resultSet = statement.executeQuery(sql); // next初始指向表头 // 5. 使用while循环取出数据 while (resultSet.next()) { // next()使光标向后移动,如果没有更多行,返回fasle int id = resultSet.getInt(1);// 获取该行的第1列的值 int id2 = resultSet.getInt("id"); // 通过列名来获取值 String name = resultSet.getString(2);// 获取该行的第2列的值 String gender = resultSet.getString(3);// 获取该行的第3列的值 Date date = resultSet.getDate(4);// 获取该行的第4列的值 System.out.println("id: " + id + "\t" + "name: " + name + "\t" + "gender: " + gender + "\t" + "date: " + date); } // 6. 关闭连接 resultSet.close(); statement.close(); connection.close(); } }

ResultSet对象存储的内容分析:

Statement类

基本介绍:

Statement对象 用于执行静态SQL语句并返回其生成的结果的对象。 在建立连接后,需要对数据库进行访问,执行命令或是SQL语句,可以通过 Statement【存在SQL注入问题,实际开发不会使用】 PreparedStatement【预处理】 CallableStatement【存储过程】 Statement对象执行SQL语句,存在SQL注入风险。 SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,恶意攻击数据库。 要防范SQL注入,只要用PreparedStatement(从Statement扩展而来)取代Statement就可以。

模拟SQL注入

输入的用户名:1' or

输入的用户密码:or '1' = '1

package com.jdbc.statement; import java.io.FileInputStream; import java.io.IOException; import java.sql.*; import java.util.Properties; import java.util.Scanner; public class Statement_ { public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException { Scanner scanner = new Scanner(System.in); // 如果想要看到SQL注入,需要使用nextLine()方法,next()方法读取到空格或者'就停止 System.out.print("请输入管理员名字:"); String admin_name = scanner.nextLine(); // 输入用户名:1' or System.out.print("请输入管理员密码:"); String admin_pwd = scanner.nextLine(); // 输入万能密码: or '1' = '1 Properties properties = new Properties(); properties.load(new FileInputStream("D:\\Program Files (x86)\\IDEA\\Projects\\Module02_Study\\src\\mysql.properties")); String user = properties.getProperty("user"); String password = properties.getProperty("password"); String driver = properties.getProperty("driver"); String url = properties.getProperty("url"); // 1.注册驱动 Class.forName(driver); // 2.得到连接 Connection connection = DriverManager.getConnection(url, user, password); // 3.得到Statement Statement statement = connection.createStatement(); // 4.编写sql String sql = "select user, password from users where user = '" + admin_name + "' and password = '" + admin_pwd + "'"; ResultSet resultSet = statement.executeQuery(sql); if (resultSet.next()) { System.out.println("登录成功"); } else { System.out.println("登陆失败"); } // 5.关闭连接 resultSet.close(); statement.close(); connection.close(); } }

PreparedStatement类

基本介绍:

PreparedStatement执行的SQL语句中的参数用问号(?)来表示,调用PreparedStatement对象的setXxx()方法来设置这些参数。setXxx()方法有两个参数,第一个参数是要设置的SQL语句中的参数的索引(从1开始,即?在SQL语句中是第几个?),第二个是设置的SQL语句中的参数的值。 调用executeQuery():返回Result对象 调用executeUpdate():执行更新,包括增、删、改。 package com.jdbc.preparedstatement_; import java.io.FileInputStream; import java.io.IOException; import java.sql.*; import java.util.Properties; import java.util.Scanner; public class PreparedStatement_ { public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException { Scanner scanner = new Scanner(System.in); // 如果想要看到SQL注入,需要使用nextLine()方法,next()方法读取到空格或者'就停止 System.out.print("请输入管理员名字:"); String admin_name = scanner.nextLine(); // 输入用户名:1' or System.out.print("请输入管理员密码:"); String admin_pwd = scanner.nextLine(); // 输入万能密码: or '1' = '1 Properties properties = new Properties(); properties.load(new FileInputStream("D:\\Program Files (x86)\\IDEA\\Projects\\Module02_Study\\src\\mysql.properties")); String user = properties.getProperty("user"); String password = properties.getProperty("password"); String driver = properties.getProperty("driver"); String url = properties.getProperty("url"); // 1.注册驱动 Class.forName(driver); // 2.得到连接 Connection connection = DriverManager.getConnection(url, user, password); // 3.得到PreparedStatement // 3.1.编写sql,sql语句中的?相当于占位符 String sql = "select user, password from users where user = ? and password = ?"; // 3.2.preparedStatement对象是PreparedStatement接口的实现类的对象 PreparedStatement preparedStatement = connection.prepareStatement(sql); // 此时sql语句已经和preparedStatement对象关联,执行sql语句时不需要再传入sql语句 // 3.3.给?赋值 preparedStatement.setString(1, admin_name); preparedStatement.setString(2, admin_pwd); // 4.执行select语句,要使用executeQuery()方法 ResultSet resultSet = preparedStatement.executeQuery(); // 不能传入sql语句 if (resultSet.next()) { System.out.println("登录成功"); } else { System.out.println("登陆失败"); } // 5.关闭连接 resultSet.close(); preparedStatement.close(); connection.close(); } } package com.jdbc.preparedstatement_; import java.io.FileInputStream; import java.io.IOException; import java.sql.*; import java.util.Properties; import java.util.Scanner; public class PreparedStatementDML_ { public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException { Scanner scanner = new Scanner(System.in); System.out.print("请输入要修改的管理员名字:"); String admin_name = scanner.nextLine(); // System.out.print("请输入要修改的管理员密码:"); // String admin_pwd = scanner.nextLine(); Properties properties = new Properties(); properties.load(new FileInputStream("D:\\Program Files (x86)\\IDEA\\Projects\\Module02_Study\\src\\mysql.properties")); String user = properties.getProperty("user"); String password = properties.getProperty("password"); String driver = properties.getProperty("driver"); String url = properties.getProperty("url"); // 1.注册驱动 Class.forName(driver); // 2.得到连接 Connection connection = DriverManager.getConnection(url, user, password); // 3.得到PreparedStatement // 3.1.编写sql,sql语句中的?相当于占位符 // Update // String sql = "update users set password = ? where user = ?"; // String sql = "insert into users(user, password) values (?,?)"; String sql = "delete from users where user = ?"; // 3.2.preparedStatement对象是PreparedStatement接口的实现类的对象 PreparedStatement preparedStatement = connection.prepareStatement(sql); // 3.3.给?赋值 preparedStatement.setString(1, admin_name); // 4.调用executeUpdate()方法,返回受影响的行数 int rows = preparedStatement.executeUpdate(); System.out.println(rows > 0 ? "修改成功" : "修改失败"); // 5.关闭连接 preparedStatement.close(); connection.close(); } } 4.3、JDBC API

4.4、JDBC Utils

在JDBC操作中,获取数据库连接和释放资源经常使用到,所以可以把获取数据库连接和释放资源操作抽出来封装成JDBC工具类JDBCUtils。

JDBCUtils.java工具类

package com.jdbc.utils; import java.io.FileInputStream; import java.io.IOException; import java.sql.*; import java.util.Properties; @SuppressWarnings({"all"}) public class JDBCUtils { // 用户名 private static String user; // 密码 private static String password; // 驱动名 private static String driver; // url private static String url; // 在static代码块完成类变量的初始化 static { Properties properties = new Properties(); try { properties.load(new FileInputStream("src\\mysql.properties")); // 从配置文件中读取相关属性值 user = properties.getProperty("user"); password = properties.getProperty("password"); driver = properties.getProperty("driver"); url = properties.getProperty("url"); } catch (IOException e) { // 在实际开发中,会将编译异常转成运行异常,抛出 // 调用者可以选择捕获该异常,也可以选择默认处理该异常,比较方便 throw new RuntimeException(e); } } /** * 获取MySQL数据库的连接的方法 * * @return :返回Connection连接对象 */ public static Connection getConnection() { try { return DriverManager.getConnection(url, user, password); } catch (SQLException e) { // 在实际开发中,会将编译异常转成运行异常,抛出 // 调用者可以选择捕获该异常,也可以选择默认处理该异常,比较方便 throw new RuntimeException(e); } } /** * 释放资源的方法:如果需要释放资源则传入对象,否则传入null * * @param resultSet :结果集对象 * @param statement :Statement或PreparedStatement对象 * @param connection :数据库连接 */ public static void close(ResultSet resultSet, Statement statement, Connection connection) { try { if (resultSet != null) { resultSet.close(); } if (statement != null) { statement.close(); } if (connection != null) { connection.close(); } } catch (SQLException e) { // 在实际开发中,会将编译异常转成运行异常,抛出 // 调用者可以选择捕获该异常,也可以选择默认处理该异常,比较方便 throw new RuntimeException(e); } } }

JDBCUtils_Use.java测试JDBCUtils类

package com.jdbc.utils; import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Scanner; public class JDBCUtils_Use { @Test public void testDML() { // 1.得到连接 Connection connection = null; // 2.编写sql // String sql = "insert into goddess(name) values(?)"; // String sql = "delete from goddess where name = ?"; String sql = "update goddess set id = ? where name = ?"; PreparedStatement preparedStatement = null; Scanner scanner = new Scanner(System.in); int id = scanner.nextInt(); String name = scanner.nextLine(); try { connection = JDBCUtils.getConnection(); System.out.println(connection.getClass()); // 运行类型:com.mysql.cj.jdbc.ConnectionImpl preparedStatement = connection.prepareStatement(sql); // 给占位符赋值 preparedStatement.setInt(1, id); preparedStatement.setString(2, name); // 执行 int rows = preparedStatement.executeUpdate(); System.out.println(rows > 0 ? "操作成功" : "操作失败"); } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtils.close(null, preparedStatement, connection); } } }

注意:

Junit单元测试无法使用scanner输入和I/O流的解决方案:

通过IDEA工具Help-Edit Custom VM Options打开配置文件位置 修改配置文件idea64.exe.vmoptions,在最后一行添加:-Deditable.java.test.console=true 重启IDEA 4.5、事务

基本介绍:

JDBC程序中当一个Connection对象创建时,默认情况下是自动提交事务:每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,提交事务后就不能回滚。 JDBC程序中为了让多个SQL语句作为一个整体执行(要么全部成功,要么全部失败),需要使用事务。 调用Connection的setAutoCommit(false)可以取消自动提交事务。 在所有的SQL语句都执行成功后,调用Connection的commit()方法提交事务。 在其中某个操作失败或出现异常时,调用Connection的rollback()方法回滚事务。

案例演示:银行转账

package com.jdbc.transaction_; import com.jdbc.utils.JDBCUtils; import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class Transaction_ { @Test public void noTransaction() { // 1.得到连接 Connection connection = null; // 2.编写sql String sql1 = "update account set balance = balance - 1000 where id = 1"; String sql2 = "update account set balance = balance + 1000 where id = 2"; PreparedStatement preparedStatement = null; try { connection = JDBCUtils.getConnection(); preparedStatement = connection.prepareStatement(sql1); preparedStatement.executeUpdate(); // 执行第1条sql语句 int a = 1 / 0; // 抛出异常,try块中下面的代码不再执行 preparedStatement = connection.prepareStatement(sql2); preparedStatement.executeUpdate(); // 执行第2条sql语句 } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtils.close(null, preparedStatement, connection); } } @Test public void useTransaction() { // 1.得到连接 Connection connection = null; // 2.编写sql String sql1 = "update account set balance = balance - 1000 where id = 1"; String sql2 = "update account set balance = balance + 1000 where id = 2"; PreparedStatement preparedStatement = null; try { connection = JDBCUtils.getConnection(); connection.setAutoCommit(false); // 设置不自动提交事务,相当于开启了事务 preparedStatement = connection.prepareStatement(sql1); preparedStatement.executeUpdate(); // 执行第1条sql语句 int a = 1 / 0; // 抛出异常,try块中下面的代码不再执行 preparedStatement = connection.prepareStatement(sql2); preparedStatement.executeUpdate(); // 执行第2条sql语句 // 如果没有抛出异常,则提交事务 connection.commit(); } catch (SQLException e) { // 当捕获了异常,这里可以回滚事务,即撤销执行的sql语句 try { // 如果rollback()不传入SavePoint保存点,则默认回滚到开启事务的状态 System.out.println("执行发生了异常,开始回滚事务"); connection.rollback(); System.out.println("已撤销执行的sql语句,事务回滚完毕"); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); } finally { JDBCUtils.close(null, preparedStatement, connection); } } } 4.6、批处理

基本介绍:

当需要成批插入或更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下单独提交处理更有效率。 JDBC的批量处理语句常用方法: addBatch(): 添加需要批量处理的SQL语句或参数 executeBatch(): 执行批量处理的语句 clearBatch(): 清空批处理包的语句 JDBC连接MySQL时,如果要使用批处理功能,必须要在url中加入参数?rewriteBatchedStatements=true 批处理往往和PreparedStatement一起搭配使用,既可以减少编译次数,有减少运行次数,效率提高。 package com.jdbc.batch_; import com.jdbc.utils.JDBCUtils; import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class TestBatch { @Test public void prepared() throws SQLException { Connection connection = JDBCUtils.getConnection(); String sql = "create table batch(id int, name varchar(25), password varchar(25))"; PreparedStatement preparedStatement = connection.prepareStatement(sql); boolean isSuccess = preparedStatement.execute(); System.out.println(isSuccess); JDBCUtils.close(null,preparedStatement,connection); } @Test public void noBatch() throws SQLException { Connection connection = JDBCUtils.getConnection(); String sql = "insert into batch(id, name, password) values (?,?,?)"; PreparedStatement preparedStatement = connection.prepareStatement(sql); System.out.println("开始执行..."); long start = System.currentTimeMillis(); for (int i = 1; i 采用数据库连接池技术

数据库连接池

基本介绍:

预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需要从“缓冲池”中取出一个,使用完毕之后再放回去。 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

数据库连接池种类:

JDBC的数据库连接池使用javax.sql.DataSource(本质上是一个接口)来表示,DataSource只是一个接口,该接口通常由第三方提供实现(提供相应的.jar包)。 C3P0数据库连接池,速度相对较慢,稳定性好(Hibernate, Spring使用) DBCP数据库连接池,速度相对C3P0较快,但不稳定。 Proxool数据库连接池,有监控连接池状态的功能,稳定性较C3P0差一点。 BoneCP数据库连接池,速度快。 Druid(德鲁伊)是阿里提供的数据库连接池,集DBCP、C3P0、Proxool优点于一身的数据库连接池(最常用,推荐)。

注意:在数据库连接池技术中,close()方法不是真正的断掉数据库连接,而是把使用的Connection对象放回连接池 -> 断掉对象的引用

C3P0数据库连接池:

ComboPooledDataSource类

常用方法:

构造器ComboPooledDataSource(String configName): 传入数据源名称 setDriverClass(String driver): 设置驱动类 setJdbcUrl(): 设置url setUser(): 设置登录数据库的用户名 setPassword(): 设置登录数据库的用户密码 setInitialPoolSize(int size): 设置初始化连接数 setMaxPoolSize(int size): 设置最大连接数 getConnection(): 获取数据库连接,返回Connection对象 package com.jdbc.datasource_; import com.jdbc.utils.JDBCUtils; import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.jupiter.api.Test; import java.io.FileInputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; public class C3P0_ { /** * 测试传统方式连接MySQL5000次 */ @Test public void testCon() throws SQLException { System.out.println("开始连接"); long start = System.currentTimeMillis(); for (int i = 0; i < 5000; i++) { Connection connection = JDBCUtils.getConnection(); // 对数据库的操作... connection.close(); // 关闭数据库 } long end = System.currentTimeMillis(); System.out.println("传统JDBC方式 连接MySQL5000次 耗时: " + (end - start)); // 传统JDBC方式 连接MySQL5000次 耗时: 31287 } /** * 方式1:相关参数,在程序中指定user,url,password等 */ @Test public void testC3P0_01() throws Exception { // 1. 创建一个数据源对象 ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); // 2. 通过配置文件获取相关的连接信息 Properties properties = new Properties(); properties.load(new FileInputStream("src\\mysql.properties")); // 从配置文件中读取相关属性值 String user = properties.getProperty("user"); String password = properties.getProperty("password"); String driver = properties.getProperty("driver"); String url = properties.getProperty("url"); // 3. 给数据源 comboPooledDataSource 设置相关信息 // 注意:连接池的连接的管理是由comboPooledDataSource 来管理 comboPooledDataSource.setDriverClass(driver); comboPooledDataSource.setJdbcUrl(url); comboPooledDataSource.setUser(user); comboPooledDataSource.setPassword(password); // 4. 设置初始化连接数 comboPooledDataSource.setInitialPoolSize(10); // 5. 最大连接数 comboPooledDataSource.setMaxPoolSize(50); long start = System.currentTimeMillis(); for (int i = 0; i < 5000; i++) { Connection connection = comboPooledDataSource.getConnection(); // 这个方法就是从DataSource接口实现的 //System.out.println("连接成功"); connection.close(); } long end = System.currentTimeMillis(); System.out.println("C3P0方式1 连接MySQL5000次 耗时: " + (end - start)); // C3P0方式1 连接MySQL5000次 耗时: 775 } /** * 方式2:使用配置文件模板来完成 */ @Test public void testC3P0_02() throws Exception { // ComboPooledDataSource(String configName) 传入数据源名称 ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("yxz_mysql"); // 测试5000次连接mysql long start = System.currentTimeMillis(); for (int i = 0; i < 5000; i++) { Connection connection = comboPooledDataSource.getConnection(); //System.out.println("连接成功"); connection.close(); } long end = System.currentTimeMillis(); System.out.println("C3P0方式2 连接MySQL5000次 耗时: " + (end - start)); // C3P0方式2 连接MySQL5000次 耗时: 856 } }

c3p0-config.xml:C3P0的配置文件

com.mysql.cj.jdbc.Driver jdbc:mysql://localhost:3306/database?characterEncoding=UTF-8 root 123456 10 60 5 100 5 com.mysql.cj.jdbc.Driver jdbc:mysql://localhost:3380/jdbc_test?characterEncoding=UTF-8 root 123456 10 30 100 10

使用C3P0注意:

需要导入C3P0的jar包 C3P0的配置文件必须名为c3p0-config.xml,会自动查找该文件, C3P0配置文件应放在项目的src目录下。

Druid数据库连接池:

package com.jdbc.datasource_; import com.alibaba.druid.pool.DruidDataSourceFactory; import org.junit.jupiter.api.Test; import javax.sql.DataSource; import java.io.FileInputStream; import java.sql.Connection; import java.util.Properties; public class Druid_ { @Test public void testDruid_() throws Exception { // 1.加入druid jar包 // 2.加入配置文件druid.properties,放在项目的src目录下 // 3.创建Properties对象,读取配置文件 Properties properties = new Properties(); properties.load(new FileInputStream("src\\druid.properties")); // 4.创建一个指定参数的Druid数据库连接池 DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); System.out.println("开始连接"); long start = System.currentTimeMillis(); for (int i = 0; i < 500000; i++) { // 5.得到连接 Connection connection = dataSource.getConnection(); // 6.关闭连接 connection.close(); } long end = System.currentTimeMillis(); System.out.println("Druid 连接MySQL500000次 耗时: " + (end - start)); // // Druid 连接MySQL500000次 耗时: 1265 } }

druid.properties:Druid的配置文件

driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3380/jdbc_test?rewriteBatchedStatements=true characterEncoding=utf-8 username=root password=123456 initialSize=5 minIdle=5 maxActive=50 maxWait=3000 validationQuery=SELECT 1 testWhileIdle=true

JDBCUtilsByDruid.java:Druid的工具类

package com.jdbc.utils; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.FileInputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; public class JDBCUtilsByDruid { private static DataSource dataSource; /* 初始化数据源 */ static { Properties properties = new Properties(); try { properties.load(new FileInputStream("src\\druid.properties")); dataSource = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } /** * 获取数据库连接对象的方法 * * @return 返回数据库连接对象 * @throws SQLException sql异常 */ public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } /** * 释放资源的方法 * * @param resultSet sql查询的结果集 * @param statement 预处理和Statement对象 * @param connection 数据库连接 */ public static void close(ResultSet resultSet, Statement statement, Connection connection) { try { if (resultSet != null) { resultSet.close(); } if (statement != null) { statement.close(); } if (connection != null) { connection.close(); } } catch (SQLException e) { throw new RuntimeException(e); } } }

测试Druid工具类

package com.jdbc.utils; import org.junit.jupiter.api.Test; import java.sql.*; @SuppressWarnings({"all"}) public class JDBCUtilsByDruid_Use { @Test public void testDruidUtils() { // 1.得到连接 Connection connection = null; // 2.编写sql String sql = "select * from goddess"; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { connection = JDBCUtilsByDruid.getConnection(); System.out.println(connection.getClass()); // 运行类型:com.alibaba.druid.pool.DruidPooledConnection preparedStatement = connection.prepareStatement(sql); // 执行 resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { int id = resultSet.getInt("id"); String name = resultSet.getString("name"); String sex = resultSet.getString("sex"); Date borndate = resultSet.getDate("borndate"); String phone = resultSet.getString("phone"); System.out.println("id:\t" + id + "\tname:" + name + "\tsex:" + sex + "\tborndate:" + borndate + "\tphone:" + phone); } } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtilsByDruid.close(resultSet, preparedStatement, connection); } } }

使用Druid数据库连接池的注意事项:

Druid配置文件druid.properties,该文件需要放在项目的src目录下 得到Druid数据库连接池对象使用

面试问题:为什么Druid和C3P0和传统的JDBC都有close()方法,它们都是关闭数据库连接吗?

答:不是。Druid和C3P0都实现了Connection接口,所以它们都有close方法,但是close()方法具体的实现不同。Druid和C3P0只是断开了数据库连接池的连接对象的引用,把使用过的Connection对象放回连接池以供其他程序使用,而数据库连接并没有断开。

Druid中有关close()方法的源码如下:

public class DruidPooledConnection extends PoolableWrapper implements PooledConnection, Connection { public void close() throws SQLException { if (!this.disable) { DruidConnectionHolder holder = this.holder; if (holder == null) { if (this.dupCloseLogEnable) { LOG.error("dup close"); } } else { DruidAbstractDataSource dataSource = holder.getDataSource(); boolean isSameThread = this.getOwnerThread() == Thread.currentThread(); if (!isSameThread) { dataSource.setAsyncCloseConnectionEnable(true); } if (dataSource.isAsyncCloseConnectionEnable()) { this.syncClose(); } else if (CLOSING_UPDATER.compareAndSet(this, 0, 1)) { try { Iterator var4 = holder.getConnectionEventListeners().iterator(); while(true) { if (!var4.hasNext()) { List filters = dataSource.getProxyFilters(); if (filters.size() > 0) { FilterChainImpl filterChain = new FilterChainImpl(dataSource); filterChain.dataSource_recycle(this); } else { this.recycle(); } break; } ConnectionEventListener listener = (ConnectionEventListener)var4.next(); listener.connectionClosed(new ConnectionEvent(this)); } } finally { CLOSING_UPDATER.set(this, 0); } this.disable = true; } } } } } 4.8、Apache—DBUtils

问题引入:

select语句查询返回的ResultSet结果集是和Connection关联的 -> 一旦close()后,无法再对ResultSet操作。 结果集不利于数据的管理 -> 只能使用一次,close后无法再使用。 使用返回的信息也不方便 -> 只能用getXxx()的方法并传入列名才能获取到值,且方法名和类型有关,做不到见名知意。

解决方案(核心思想):

案例:查询goddess表中所有记录

Goddess.java

package com.jdbc.datasource_; import java.util.Date; public class Goddess { // JavaBean,POJO,Domain对象 private Integer id; private String name; private String sex; private Date borndate; private String phone; public Goddess() { // 必须声明一个无参构造器,反射需要 } public Goddess(Integer id, String name, String sex, Date borndate, String phone) { this.id = id; this.name = name; this.sex = sex; this.borndate = borndate; this.phone = phone; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Date getBorndate() { return borndate; } public void setBorndate(Date borndate) { this.borndate = borndate; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Override public String toString() { return "\nGoddess{" + "id=" + id + ", name='" + name + '\'' + ", sex='" + sex + '\'' + ", borndate=" + borndate + ", phone='" + phone + '\'' + '}'; } }

JDBCUtilsByDruid_Use.java测试类

package com.jdbc.datasource_; import com.jdbc.utils.JDBCUtilsByDruid; import org.junit.jupiter.api.Test; import java.sql.*; import java.util.ArrayList; @SuppressWarnings({"all"}) public class JDBCUtilsByDruid_Use { /** * 使用传统方法将查询到的resultSet记录封装到Goddess对象,放入在ArrayList集合 */ @Test public void testSelectToArrayList() { // 1.得到连接 Connection connection = null; // 2.编写sql String sql = "select * from goddess"; PreparedStatement preparedStatement = null; ResultSet resultSet = null; ArrayList goddesses = new ArrayList(); // 创建ArrayList,存放Goddess对象 try { connection = JDBCUtilsByDruid.getConnection(); preparedStatement = connection.prepareStatement(sql); // 执行 resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { int id = resultSet.getInt("id"); String name = resultSet.getString("name"); String sex = resultSet.getString("sex"); Date borndate = resultSet.getDate("borndate"); String phone = resultSet.getString("phone"); goddesses.add(new Goddess(id, name, sex, borndate, phone)); } for (Goddess goddess : goddesses) { System.out.println("name:" + goddess.getName() + "\tborndate:" + goddess.getBorndate()); } } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtilsByDruid.close(resultSet, preparedStatement, connection); } // 因为ArrayList集合和connection没有任何关联,所以可以在connection关闭后访问ArrayList System.out.println("list集合数据:" + goddesses); } }

Apache-DBUtils类

基本介绍:

commons-dbutils 是Apache组织提供的一个开源JDBC工具类库,它是对JDBC的封装,使用dbutils能极大简化jdbc编码的工作量。 QueryRunner类:该类封装了SQL的执行,是线程安全的。可以实现增、删、改、查、批处理。使用QueryRunner类实现查询操作。 ResultSetHandler接口:该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式。 ArrayHandler:把结果集中的第一行数据转成对象数组。 ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。 BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。 BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。 ColumnListHandler:将结果集中某一列的数据存放到List中。 KeyedHandler(name):将结果集中的每行数据都封装到Map里,再把这些map再存到一个map里,其key为指定的key。 MapHandler:将结果集中的每一行数据封装到一个Map里,key是列名,value是对应的值。 MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List。 package com.jdbc.datasource_; import com.jdbc.utils.JDBCUtilsByDruid; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.apache.commons.dbutils.handlers.ScalarHandler; import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.SQLException; import java.util.List; @SuppressWarnings({"all"}) public class DBUtils_Use { /** * 使用Apache-DBUtils工具类 + Druid数据库连接池完成对goddess表的crud操作 */ @Test public void testQueryMany() throws SQLException { // 1.得到连接 Connection connection = JDBCUtilsByDruid.getConnection(); // 2.使用DBUtils类和接口,先导入DBUtils和相关jar包:commons-dbutils-1.7.jar,加入到本Project // 3.创建QueryRunner QueryRunner queryRunner = new QueryRunner(); // 4.执行相关方法,返回ArrayList结果集 String sql = "select * from goddess where id >= ?"; /* query方法解读: 1.query方法就是执行sql语句,得到resultSet --封装到--> ArrayList集合中 2.返回集合List 3.connection:连接 4.sql:要执行的sql语句 5.new BeanListHandler(Goddess.class):将resultSet --封装成--> Goddess对象 --> 添加到ArrayList中 底层使用反射机制,获取Goddess类属性进行封装 6. 1:给sql中的占位符?赋值,可以有多个值,因为最后的参数是可变参数Object... params 7.底层得到的resultSet会由query()方法关闭,底层创建的preparedStatement也会关闭 */ /** * 对queryRunner.query()方法源码分析: * private T query(Connection conn, boolean closeConn, String sql, ResultSetHandler rsh, Object... params) throws SQLException { * if (conn == null) { * throw new SQLException("Null connection"); * } else if (sql == null) { * if (closeConn) { * this.close(conn); * } * * throw new SQLException("Null SQL statement"); * } else if (rsh == null) { * if (closeConn) { * this.close(conn); * } * * throw new SQLException("Null ResultSetHandler"); * } else { * PreparedStatement stmt = null; * ResultSet rs = null; * Object result = null; * * try { * stmt = this.prepareStatement(conn, sql); // 创建preparedStatement * this.fillStatement(stmt, params); // 对sql语句中的?赋值 * rs = this.wrap(stmt.executeQuery()); // 执行sql语句返回resuletSet * result = rsh.handle(rs); // 返回的resuletSet -> ArrayList[使用反射机制,对传入的class对象处理] * } catch (SQLException var33) { * this.rethrow(var33, sql, params); * } finally { * try { * this.close(rs); // 关闭resuletSet * } finally { * this.close(stmt); // 关闭preparedStatement * if (closeConn) { * this.close(conn); // 关闭数据库连接 * } * * } * } * * return result; // 返回ArrayList * } * } */ List list = queryRunner.query(connection, sql, new BeanListHandler(Goddess.class), 1); for (Goddess goddess : list) { System.out.print(goddess); } // 5.释放资源 JDBCUtilsByDruid.close(null, null, connection); } /** * 查询单行记录多列的情况,传入BeanHandler对象,query方法返回Bean对象,如果没有记录则返回null * * @throws SQLException */ @Test public void testQuerySingle() throws SQLException { Connection connection = JDBCUtilsByDruid.getConnection(); QueryRunner queryRunner = new QueryRunner(); String sql = "select * from goddess where id = ?"; Goddess goddess = queryRunner.query(connection, sql, new BeanHandler(Goddess.class), 1); System.out.println(goddess); JDBCUtilsByDruid.close(null, null, connection); } /** * 查询单行单列的情况,使用ScalarHandler,query方法返回Object对象,如果没有记录则返回null * * @throws SQLException */ @Test public void testScalar() throws SQLException { Connection connection = JDBCUtilsByDruid.getConnection(); QueryRunner queryRunner = new QueryRunner(); String sql = "select name from goddess where id = ?"; // 查询单行记录的某一列值,使用ScalarHandler Object name = queryRunner.query(connection, sql, new ScalarHandler(), 1); System.out.println(name); JDBCUtilsByDruid.close(null, null, connection); } /** * queryRunner.update方法测试DML操作 * * @throws SQLException */ @Test public void testDML() throws SQLException { Connection connection = JDBCUtilsByDruid.getConnection(); QueryRunner queryRunner = new QueryRunner(); String sql = "insert into users values (?,?,?)"; // update()方法 可以执行增删改的操作,返回值表示受影响的行数 int affectedRows = queryRunner.update(connection, sql, 2, "yxz", "66666"); System.out.println(affectedRows > 0 ? "执行成功" : "操作对数据库无影响"); JDBCUtilsByDruid.close(null, null, connection); } }

在自定义Bean类时的注意事项:

Bean类中的属性名称必须和数据库表中的字段名保持一致!!!(因为反射根据Bean加载类信息需要)

Bean类中必须为所有属性设置get、set方法,反射需要

Bean类中属性的类型全部使用包装类,不要使用基本数据类型,因为MySQL中所有数据类型都可能为null,而Java中只有引用类型才会有null值。

数据库表中char类型的字段,在Bean类中对应属性使用String类型

数据库表中date类型的字段,在Bean类中对应属性使用java.util.Date类型

Bean类的属性名大小写不用管,因为源码中使用了equalsIgnoreCase()方法进行比较

4.9、DAO增删改查—BasicDao

使用Apache-DBUtils + Druid虽然简化了JDBC开发,但还有不足:

SQL语句是固定的,不能通过参数传入,通用性不好,需要改进 -> 更方便执行增删改查。 对于select操作,如果有返回值,返回类型不确定,需要使用泛型。 数据表一旦变多,业务复杂,不可能只靠一个Java程序完成。 关于BasicDAO的思想示意图:

简单设计:com.jdbc.dao_

com.jdbc.dao_.utils工具类

JDBCUtilsByDruid package com.jdbc.dao_.utils; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.FileInputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; public class JDBCUtilsByDruid { private static DataSource dataSource; /* 初始化数据源 */ static { Properties properties = new Properties(); try { properties.load(new FileInputStream("src\\druid.properties")); dataSource = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } /** * 获取数据库连接对象的方法 * * @return 返回数据库连接对象 * @throws SQLException sql异常 */ public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } /** * 释放资源的方法 * * @param resultSet sql查询的结果集 * @param statement 预处理和Statement对象 * @param connection 数据库连接 */ public static void close(ResultSet resultSet, Statement statement, Connection connection) { try { if (resultSet != null) { resultSet.close(); } if (statement != null) { statement.close(); } if (connection != null) { connection.close(); } } catch (SQLException e) { throw new RuntimeException(e); } } }

com.jdbc.dao_.domainJavaBean类

Goddess类 package com.jdbc.dao_.domain; import java.util.Date; public class Goddess { // JavaBean,POJO,Domain对象 private Integer id; private String name; private String sex; private Date borndate; private String phone; public Goddess() { // 必须声明一个无参构造器,反射需要 } public Goddess(Integer id, String name, String sex, Date borndate, String phone) { this.id = id; this.name = name; this.sex = sex; this.borndate = borndate; this.phone = phone; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Date getBorndate() { return borndate; } public void setBorndate(Date borndate) { this.borndate = borndate; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Override public String toString() { return "Goddess{" + "id=" + id + ", name='" + name + '\'' + ", sex='" + sex + '\'' + ", borndate=" + borndate + ", phone='" + phone + '\'' + '}'; } }

com.jdbc.dao_.daoDAO类

BasicDAO类 package com.jdbc.dao_.dao; import com.jdbc.dao_.utils.JDBCUtilsByDruid; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.apache.commons.dbutils.handlers.ScalarHandler; import java.sql.Connection; import java.sql.SQLException; import java.util.List; @SuppressWarnings({"all"}) public class BasicDAO { // 泛型指定其他类型 private QueryRunner queryRunner = new QueryRunner(); /** * 通用的update方法,针对任意的表,执行增删改的操作 * * @param sql 要执行的sql语句 * @param parameters sql语句中的?的值 * @return 返回执行sql语句后,数据库中受影响的行数 */ public int update(String sql, Object... parameters) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return queryRunner.update(connection, sql, parameters); } catch (SQLException e) { throw new RuntimeException(e); } finally { JDBCUtilsByDruid.close(null, null, connection); } } /** * 查询多行结果的通用方法 * * @param sql 要执行的select语句 * @param cls 传入一个类的Class对象,如Goddess.class * @param parameters 可变参数,传入?的具体值,可以是多个 * @return 根据cls.class对象,返回对应的ArrayList集合 */ public List queryMulti(String sql, Class cls, Object... parameters) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return queryRunner.query(connection, sql, new BeanListHandler(cls), parameters); } catch (SQLException e) { throw new RuntimeException(e); } finally { JDBCUtilsByDruid.close(null, null, connection); } } /** * 查询单行结果的通用方法 * * @param sql 要执行的select语句 * @param cls 传入一个类的Class对象,如Goddess.class * @param parameters 可变参数,传入?的具体值,可以是多个 * @return 根据cls.class对象,返回对应的T对象 */ public T querySingle(String sql, Class cls, Object... parameters) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return queryRunner.query(connection, sql, new BeanHandler(cls), parameters); } catch (SQLException e) { throw new RuntimeException(e); } finally { JDBCUtilsByDruid.close(null, null, connection); } } /** * 查询单行单列结果的通用方法 * * @param sql 要执行的select语句 * @param parameters 可变参数,传入?的具体值,可以是多个 * @return 根据cls.class对象,返回Obejct对象 */ public Object queryScalar(String sql, Object... parameters) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return queryRunner.query(connection, sql, new ScalarHandler(), parameters); } catch (SQLException e) { throw new RuntimeException(e); } finally { JDBCUtilsByDruid.close(null, null, connection); } } } GoddessDAO类 package com.jdbc.dao_.dao; import com.jdbc.dao_.domain.Goddess; public class GoddessDAO extends BasicDAO{ // 1.继承父类BasicDAO的方法 // 2.根据业务需求,可以编写GoddessDAO的特有方法 }

com.jdbc.dao_.test测试类

TestDAO类 package com.jdbc.dao_.test; import com.jdbc.dao_.dao.GoddessDAO; import com.jdbc.dao_.domain.Goddess; import org.junit.jupiter.api.Test; import java.util.List; public class TestDAO { /** * 测试GoddessDAO 对 goddess表的操作 */ @Test public void testGoddessDAO() { GoddessDAO goddessDAO = new GoddessDAO(); // 1.查询多行记录 List list = goddessDAO.queryMulti("select * from goddess where id < ?", Goddess.class, 20); for (Goddess goddess : list) { System.out.println(goddess); } // 2.查询单行记录 Goddess goddess = goddessDAO.querySingle("select * from goddess where id = ?", Goddess.class, 1); System.out.println("============================单行记录查询结果==============================="); System.out.println(goddess); // 3.查询单行单列 Object name = goddessDAO.queryScalar("select name from goddess where id = ?", 1); System.out.println("==========================单行单列记录查询结果============================"); System.out.println(name); } } 5、正则表达式 5.1、入门

正则表达式(Regular Expression, regexp):对字符串(文本)执行模式匹配的技术。

基本介绍:

一个正则表达式,就是用某种模式去匹配字符串的一个公式。 不是只有Java有正则表达式,很多语言都支持正则表达式进行字符串操作:JavaScript、PHP、Java、python等。

源码剖析:

package regexp; import java.util.regex.Matcher; import java.util.regex.Pattern; @SuppressWarnings({"all"}) public class RegTheory { public static void main(String[] args) { String content = "1991年4月,Java之父James Gosling带领绿色计划(Green Project)项目启动,定位于消费电子产品(机顶盒、冰箱、收音机)运行架构的Oak语言诞生,这也是Java的前身,但是市场反响一般。\n" + "1995年5月23日,随着互联网浪潮在1995年兴起,Oak迅速蜕,Java语言诞生,在SunWorld大会上发布Java1.0,第一次提出Write Once,Run Anywhere的口号。\n" + "1996年1月23日,JDK1.0发布,纯解释型的Java虚拟机(Sun Classic VM)、Applet、AWT等诞生。\n" + "1996年4月,十个最主要的操作系统和计算机供应商声明将在其产品中嵌入Java技术,\n" + "1996年5月底,Sun于美国旧金山举行了首届JavaOne大会,从此JavaOne成为全世界数百万Java开发者每年一度的技术盛会。\n" + "1996年9月,已有大约8.3万个网页应用了Java技术来制作。\n" + "1997年2月19日,Sun公司发布了JDK1.1,代表技术:JAR文件格式、JDBC、JavaBeans、RMI等,Java语法也进行了增强,内部类(Inner Class)和反射(Reflection)出现。\n" + "1998年12月4日,JDK1.2发布,这是一个里程碑式的重要版本,工程代号为Playground(竞技场),这个版本代表性技术非常多,如EJB、JavaPlug-in、Swing、JavaIDL等,还有使用极为频繁的Collections体系工具类等,并且这个版本中Java虚拟机第一次内置了JIT(Just In Time)即时编译器,后续还发布了JDK1.2.1和JDK1.2.2两个小版本升级。在JDK1.2中曾经共存过ClassicVM、HotSpotVM、ExactVM三个虚拟机,其中HotSpot是作为附加程序提供。也是在这个版本中Sun开始拆分对应的Java产品线,Sun在这个版本中把Java技术体系拆分为三个方向:\n" + "分别是面向桌面应用开发的J2SE(Java 2 Platform,Standard Edition)\n" + "面向企业级开发的J2EE(Java 2 Platform,Enterprise Edition)\n" + "面向手机等移动终端开发的J2ME(Java 2 Platform,Micro Edition)\n" + "1999年4月27日,HotSpot虚拟机诞生,HotSpot最初由Longview Techno-logies公司研发,后来在1997年被Sun公司收购,后来它成为JDK 1.3及之后所有JDK版本的默认Java虚拟机。\n" + "2000年5月8日,工程代号为Kestrel(美洲红隼)的JDK 1.3发布。JDK 1.3的改进主要体现在Java类库上(如数学运算和新的Timer API等),此外一直作为扩展服务的JNDI服务也开始作为一项平台级服务被提供,还有基于CORBA IIOP协议来实现通信段RMI也出现了。\n" + "2001年5月17日,JDK1.3的修订版JDK1.3.1发布,工程代号Ladybird(瓢虫)。从JDK1.3开始,Sun公司维持着稳定的开发节奏,大概每个两年发布一个主要版本,期间发布的各个修订版本以昆虫作为工程代号。\n" + "2002年2月13日,JDK 1.4发布,工程代号为Merlin(灰背隼)。JDK 1.4是标志着Java真正走向成熟的一个版本。Compaq、SAS、Fujitsu、Symbian、IBM等一众大公司参与功能规划,甚至实现自己的独立发行版本。哪怕在二十年后的今天一些主流功能也可以在JDK1.4上运行,这个版本的主要代表技术包含正则表达式、异常链、NIO、日志类、XML解析器和XSLT转换器等等。\n" + "2002年9月16日,工程代号为Grasshopper(蚱蜢)的JDK1.4.1修订版发布。在这一年前后微软平台的.NET Framework发布,至此Java平台和.NET平台的竞争开始拉开了序幕。但似乎Java的开源策略更胜一筹,终于在2014 年 11 月 12 日,微软正式宣布了.NET Core 的开源。\n" + "2003年6月26日,工程代号为Mantis(螳螂)的JDK1.4.2修订版发布。\n" + "2004年9月30日,JDK 5发布,工程代号为Tiger(老虎),Sun公司从这个版本开始放弃JDK 1.x的命名方式,将产品版本号修改成了JDK x,JDK1.2以来Java语言在语法上的改动都不大,该版本在语法易用性上做出了非常大的改进,如自动装箱、泛型、动态注解、枚举、可变长参数、foreach等。此外改进了Java的内存模型(Java Memory Model,JMM)、提供了java.util.concurrent并发包(由Doug Lea大师带Java进入了并发时代)等,JDK 5是官方声明可以支持Windows 9x操作系统的最后一个版本。\n" + "2006年11月13日,JavaOne大会上,Sun公司宣布计划要把Java开源,随后在一年多的时间内,陆续的将JDK各部分在GPL V2协议下公开源码,随后并建立了OpenJDK组织对这些源码进行独立管理,除了极少部分的产权代码,OpenJDK几乎拥有了SunJDK 7中的全部代码。\n" + "2006年12月11日,JDK6发布,工程代号为Mustang(野马)。在这个版本中,Sun公司终结了从JDK 1.2开始已经有八年历史的J2EE、J2SE、J2ME的产品线命名方式,启用Java EE 6、Java SE 6、Java ME 6的新命名来代替。在JDK 6中提供了众多改进,如通过Mozilla JavaScript Rhino引擎提供初步动态语言支持,提供编译器注解处理器(Annotation Processor这也是Lombok的原理,通过注解生成模板代码)和微型HTTP服务器API,以及对虚拟机内部锁、同垃圾收集、类加载机制等方面进行了大量优化改动。在JDK 6发布以后由于代码的复杂化,Java开源、开发JavaFx、世界经济危机以及Oracle对Sun的收购提案等原因,Sun公司内忧外患自顾不暇,原本稳定的开发进度也受到了很大的影响,使得JDK 6的生命周期也持续了很久,一共发布了211个更新补丁,最终版本为Java SE 6 Update 211,于2018年10月18日发布。\n" + "2009年2月19日,工程代号为Dolphin(海豚)的JDK 7发布,这是其第一个里程碑版本,按照规划,共有十个里程碑版本发布,最后一个里程碑版本发布与2010年9月9日,由于各种原因JDK 7没有按照原计划完成。JDK 7开发阶段Sun公司在技术竞争和商业竞争中都深陷泥潭,已经无力推动开发进展,为了尽快完成JDK 7的发布,因此裁掉了大部分原定的功能,对于原定的Lambdax项目、Jigsaw项目、动态语言支持、Gabage-First垃圾收集器、Coin项目只匆匆交付了处于Experimental状态的G1垃圾收集器(直到2012年4月的Update 4中才开始正式商用),其余项目延期到JDK 8中。Oracle从JDK 7开始进行接手,迅速展现出了其极具商业化的处世风格,面对Java中使用最广泛的Java SE免费产品线,定义了一套新的商业版产品Java SE Support ,此外JDK 7计划维护到2022年,已经面向付费用户发布了211个补丁,最新版本为JDK 7 Update 211。\n" + "2009年4月20日,Oracle宣布正式以74亿美元的价格收SUN公司,一代巨头由此没落,Java商标正式划归Oracle所有,Java语言本身并不属于哪间公司所有,它由JCP组织进行管理。此外Oracle还收购了BEA公司,JavaEE服务器Weblogic就是该公司的产品。\n" + "2011年7月28日,JDK7发布,做出的改进:提供新的G1收集器、加强对非Java语言的调用支持、可并行的类加载架构等。\n" + "2014年3月18日,Oracle公司发布JDK 8,从此使用JEP(JDK Enhancement Proposals)来定义和管理纳入新版JDK发布范围的功能特性,JDK 8中实现了JDK7中定义并且未完成的功能,其中也有被延期到JDK 9的Jigsaw模块化功能。\n" + "JEP 126 Lambda函数表达式支持\n" + "JEP 104 内置Nashorn JavaScript引擎\n" + "JEP 150 新的时间日期API\n" + "JEP 122 移除HotSpt永久代"; // 1.\\d表示一个数字 String regStr = "(\\d\\d)(\\d\\d)"; // 2.创建一个模式对象(即正则表达式对象) Pattern pattern = Pattern.compile(regStr); // 3.创建一个匹配器(根据正则表达式的规则去匹配content字符串) Matcher matcher = pattern.matcher(content); // 4.开始匹配 /** * 源码剖析: * matcher.find() 完成的任务:考虑分组() * 什么是分组:(\\d\\d)表示第1组,第2个()表示第2组 * 1.根据指定的规则,定位满足规则的子字符串(比如1991),1991对应的开始索引:0,结束索引:3 * 2.找到后,将子字符串的开始的索引记录到matcher对象的属性int[] groups * groups[0]中,即groups[0] = 0,把该(子字符串的结束的索引+1)的值记录到groups[1] = 4; * 有分组的情况: * 2.1 groups[0] = 0,把该(子字符串的结束索引+1)的值记录到groups[1] = 4 * 2.2 记录第1组()匹配到的字符串groups[2] = 0,groups[3] = 2 * 2.3 记录第2组()匹配到的字符串groups[4] = 2,groups[5] = 4 * 如果有更多的分组,依此类推。。。 * 3.同时记录 oldLast 的值为该(子字符串的结束的索引+1的值),即4 * * matcher.group(0) 分析: * public String group(int group) { * if (first < 0) * throw new IllegalStateException("No match found"); * if (group < 0 || group > groupCount()) * throw new IndexOutOfBoundsException("No group " + group); * if ((groups[group*2] == -1) || (groups[group*2+1] == -1)) * return null; * return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString(); * } * 1.把根据groups[0] 和 groups[1]记录的位置从content开始截取子字符串并返回 -> [0,4) * 2.如果再次指向find()方法,仍然按照上面的步骤继续执行, * 即如果再次找到符合的子字符串,则新子字符串的开始索引和(结束索引+1)重新覆盖groups[0]和groups[1] */ while (matcher.find()) { /* 小结: 1.如果正则表达式有()即分组 2.取出匹配的字符串规则如下: 3.group(0)表示匹配到的整体的子字符串 4.group(1)表示匹配到的字符串的第1个子字符串的第1组 5.group(2)表示匹配到的字符串的第1个子字符串的第2组 但是group(n)中n的值不能取 超出分组数即模式中 ()的对数 */ System.out.println("找到:" + matcher.group(0)); System.out.println("第1组()匹配到的值="+matcher.group(1)); System.out.println("第2组()匹配到的值="+matcher.group(2)); // 第一次循环的输出: //找到:1991 //第1组()匹配到的值=19 //第2组()匹配到的值=91 } } } 5.2、元字符

基本介绍:

元字符(Metacharacter)-转义号\\:

\\说明:在Java语言中,\\代表其他语言中一个.

需要用到转义符号的字符:. * + ( ) $ / \ ? [ ] ^ { }

符号 含义 示例 解释 匹配输入 [] 可接收的字符列表 [abcd] 匹配a、b、c、d中的任意一个字符 [^] 不接收的字符列表 [^abc] 匹配除了a、b、c之外的任意一个字符,包括数字和特殊符号 - 连字符 A-Z 匹配任意单个大写字母 . 匹配除\n以外的任何字符 a..b 以a开头,b结尾,中间包括2个任意字符的长度为4的字符串 aaab、a35b、a#*b \\d 匹配单个数字字符,相当于[0-9] \\d{3}(\\d)? 包含3个或4个数字的字符串 123、9876 \\D 匹配单个非数字字符,相当于[^0-9] \\D(\\d)* 以单个非数字字符开头,后接任意个数字字符串 a、A342 \\w 匹配单个数字、大小写字母字符,相当于[0-9a-zA-Z] \\d{3}\\w 以3个数字字符开头的长度为7的数字字符串 234abcd、12345Pe \\W 匹配单个非数字、大小写字母字符,相当于[^0-9a-zA-Z] \\W+\\d 以至少1个非数字字母字符开头,2个数字字符结尾的字符串 #29、#[email protected] \\s 匹配任何空白字符 \\S 匹配任何非空白字符 5.2.1、限定符

限定符:用于指定其前面的字符和组合项连续出现多少次。

符号 含义 示例 说明 匹配项 * 指定字符重复0次或n次(相当于无要求) (abc)* 仅包含任意个abc的字符串 abc、abcabc + 指定字符重复1次或n次(至少一次) m+(abc)* 以至少1个m开头,后接任意个abc的字符串 m、mabc、mabcabc ? 指定字符重复0次或1次(最多一次) m+abc? 以至少1个m开头,后接ab或abc的字符串,?作用于最近的字符 mab、mabc、mmmabc 只能输入n个字符 [abcd] 由abcd中字母组成的任意长度为3的字符串 abc、dbc、adc 指定至少n个匹配 [abcd] 由abcd中字母组成的任意长度不小于3的字符串 aab、dbc、aaabdc 指定至少n个但不多于m个匹配 [abcd] 由abcd中字母组成的任意长度不小于3且不大于5的字符串 abc、abcd、aaaaa

注意:Java中匹配默认贪婪匹配,即尽可能匹配多的。

Java中汉字的编码范围:[\u0391-\uffe5]

5.2.2、选择匹配符

选择匹配符(|):在匹配某个字符串的时候是选择性的,即:既可以匹配这个,也可以匹配另一个。如:ab|cd:表示匹配ab或cd

5.2.3、字符匹配符 [a-z]:可以匹配a-z中任意一个字符 Java正则表达式默认是区分字母大小写的,如何实现不区分大小写? (?i)abc:表示abc都不区分大小写 a(?i)bc:表示bc不区分大小写 a((?i)b)c:表示只有b不区分大小写 Pattern pat = Pattern.compile(regEx, Pattern.CASE_INSENSITIVE);表示匹配时不区分字母大小写。 5.2.4、定位符

定位符:规定要匹配的字符串出现的位置,比如在字符串的开始还是在结束的位置。

符号 含义 示例 说明 匹配项 ^ 指定起始字符 [1]+[a-z]* 以至少1个数字开头,后接任意个小写字母的字符串 123、6aa、555edf $ 指定结束字符 [2]\-[a-z]+$ 以1个数字开头后接连字符-,并以至少1个小写字母结尾的字符串 1-a \\b 匹配目标字符串的边界 yxz\\b 字符串的边界指的是子串间有空格,或者是目标字符串的结束位置 wjdsfdsayxz fasdyxz \B 匹配目标字符串的非边界 yxz\\B 和\\b相反 yxzashfgkue 5.3、三个常用类 5.3.1、Pattern

Pattern对象是一个正则表达式对象,Pattern类没有公共构造方法。要创建一个Pattern对象,调用其公共静态方法compile(),它返回一个Pattern对象。该方法接受一个正则表达式作为它的第一个参数。如:Pattern pattern = Pattern.compile(regStr);

matches()方法: 整体匹配,如果正则表达式对象和字符串整体一致,则返回true,如果正则只是匹配到字符串的一部分,则返回false。

5.3.2、Matcher

Matcher对象是对输入字符串进行解释和匹配的引擎。与Pattern类一样,Matcher也没有公共构造方法。需要调用Pattern对象的matcher()方法来获得一个Matcher对象。

replaceAll(String str)方法:将匹配到的内容替换为str

5.3.3、PatternSyntaxException

PatternSyntaxException是一个非强制异常类,它表示一个正则表达式模式中的语法错误。

5.4、分组、捕获、反向引用

捕获分组:

常用分组构造形式 说明 (pattern) 非命名捕获。捕获匹配的子字符串。编号为0的第1个捕获是由整个正则表达式模式匹配的文本,其他捕获结果则根据左括号的顺序从1开始自动编号 (?pattern) 命名捕获。将匹配的子字符串捕获到一个组名称或编号名称中。用于name的字符串不能包含任何标点符号,并且不能以数字开头。可以使用单引号替代尖括号,例如:?'name'

非捕获分组:

常用分组构造形式 说明 (?:pattern) 匹配pattern但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用“or”字符(|)组合模式部件的情况很有用。如:`'industr(?:y (?=pattern) 它是一个非捕获匹配。如:`'Windows(?=95 (?!pattern) 该表达式匹配不处于匹配pattern的字符串的起始点的搜索字符串。它是一个非捕获匹配。如:`'Windows(?!95

分组:

可以用圆括号组成一个比较复杂的匹配模式,那么一个圆括号的部分可以看作是一个子表达式(一个分组)。

捕获:

把正则表达式中子表达式/分组匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用,从左向右,以分组的左括号为标志,第一个出现的组号为1,第二个为2,以此类推。组0代表的是整个正则式。

反向引用:

前提:圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式,称为反向引用。这个引用既可以是正则表达式内部,也可以是在正则表达式外部。内部反向引用:\\分组号,外部反向引用:$分组号。

案例:

要匹配两个连续的相同数字:(\\d)\\1 要匹配五个连续的相同数字:(\\d)\\1{4} 要匹配个位与千位相同,十位与百位相同的数(5225,1551):(\\d)(\\d)\\2\\1 要匹配商品编号,形式如:12321-333999111的号码,要求满足前面是一个五位数,然后一个-号,然后是一个九位数,连续的每三位数要相同:\\d{5}-(\\d)\\1{2}(\\d)\\2{2}(\\d)\\3{2}

经典案例:结巴去重

package regexp; import java.util.regex.Matcher; import java.util.regex.Pattern; public class RegExpDistinct { public static void main(String[] args) { String content = "我...我要....学学学学编程java!"; // 1.去掉所有‘.’ Pattern pattern = Pattern.compile("\\."); Matcher matcher = pattern.matcher(content); content = matcher.replaceAll(""); // System.out.println("content:" + content); // // // 2.去掉重复字,使用(.)\\1+ // // content:我我要学学学学编程java! // pattern = Pattern.compile("(.)\\1+"); // 分组捕获到的内容记录到$1 // matcher = pattern.matcher(content); // 注意:因为正则表达式变化,需要重新获取matcher // while (matcher.find()) { // System.out.println("找到:" + matcher.group(0)); // } // // 使用反向外部引用$1 来替换匹配到的内容 // content = matcher.replaceAll("$1"); // System.out.println("content=" + content); // 3.一条语句可以实现去重 content = Pattern.compile("(.)\\1+").matcher(content).replaceAll("$1"); System.out.println("content = " + content); } }

String类中关于正则表达式的方法

String replaceAll(String regex, String replacement):根据正则表达式regex将str中匹配到的内容替换成replacement,返回String类型

Boolean matches(String regex):根据正则表达式regex进行整体匹配,返回true或false。

String[] split(String regex):根据正则表达式regex匹配上的内容进行分割字符串,返回一个String[]。

package regexp; public class StringReg { public static void main(String[] args) { String content = "JDK1.2, JDK1.3, JDK1.4都是小版本"; content = content.replaceAll("JDK1\\.2|JDK1\\.3|JDK1\\.4", "JDK"); System.out.println(content); // 验证手机号:13688889999,必须以138、139 开头 content = "13888889999"; if (content.matches("1(38|39)\\d{8}")) { System.out.println("验证成功"); } else { System.out.println("验证失败"); } // 要求按照# 或者 - 或者 ~ 或者 数字 来分割 content = "hello#abc-myName~jack98北京"; String[] split = content.split("#|-|~|\\d+"); for (String s : split) { System.out.println(s); } } } 5.5、贪婪匹配和非贪婪匹配

Java中默认匹配都是贪婪匹配,即匹配尽可能多的。

可以在限定符后加上?表示非贪婪匹配。当?紧随其他限定符(*,+,?,{n},{n,},{n,m})之后时,匹配模式是“非贪婪匹配”。非贪婪匹配模式匹配搜索到的尽可能短的字符串。而默认的贪心匹配模式匹配搜索到的尽可能长的字符串。例如:在字符串“oooo”中,o+?只匹配单个o,而o+匹配结果为oooo。

5.6、应用实例

正则表达式匹配URL地址

package regexp; import java.util.regex.Matcher; import java.util.regex.Pattern; @SuppressWarnings({"all"}) public class RegExpURL { public static void main(String[] args) { /** * 正则表达式匹配url * 1.先确定url开头 http:// 或 https://,确定正则匹配模式^((http|https)://)? * 2.然后通过([\w-]+\.)+([\w-])+ 匹配域名 www.bilibili.com * 3.匹配url剩余部分(\\/[\\w-?=&/%.#_]*)?$ */ String content = "https://www.bilibili.com/video/BV1fh411y7R8?p=894&vd_source=61c88ed5566b8e501164b2b249e61557"; String regStr = "^((http|https)://)?([\\w-]+\\.)+[\\w-]+(\\/[\\w-?=&/%.#_]*)?$"; Pattern pattern = Pattern.compile(regStr); Matcher matcher = pattern.matcher(content); if (matcher.find()) { System.out.println("满足格式"); } else { System.out.println("不满足格式"); } } } 6、JDK新特性 Java 8新特性

基本介绍:

Java8(又称jdk1.8)是Java语言开发的一个重要版本。Java8是oracle公司于2014年3月发布,可以看成是自Java5以来最具革命性的版本。Java 8为Java语言、编译器、类库、开发工具和JVM带来大量新特性。

优点:

速度更快 代码更少(新的语法:Lambda表达式) 强大的Stream API 便于并行 最大化减少空指针异常:Optional类 Nashorn引擎,允许在JVM上允许JS应用 Lambda表达式

Lambda是一个匿名函数,可以把Lambda表达式理解为一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。

快速入门:

package new_characteristic.jdk8; import org.junit.jupiter.api.Test; import java.util.Comparator; public class LambdaTest { @Test public void test1() { Runnable r1 = new Runnable() { @Override public void run() { System.out.println("我爱Java"); } }; r1.run(); // 改写成Lambda表达式: Runnable r2 = () -> System.out.println("我爱python"); r2.run(); } @Test public void test2() { Comparator com1 = new Comparator() { @Override public int compare(Integer o1, Integer o2) { return Integer.compare(o1, o2); } }; System.out.println(com1.compare(31, 29)); // 改写成Lambda表达式: Comparator com2 = (o1, o2) -> Integer.compare(o1, o2); System.out.println(com2.compare(520, 1314)); // 方法引用的方式: Comparator com3 = Integer :: compare; System.out.println(com3.compare(321, 123)); } }

Lambda表达式的基本语法:

举例:(o1, o2) -> Integer.compare(o1, o2);

格式:

->:Lambda表达式的操作符,也称箭头操作符 ->左边:Lambda表达式的形参列表,其实就是接口中的抽象方法的形参列表。 ->右边:Lambda体。其实就是重写的抽象方法的方法体。

Lambda表达式的本质:作为函数式接口的实例 -> 万事万物皆对象

Lambda表达式的6种语法格式:

lambda无参,无返回值 lambda有参,无返回值 数据类型可以省略,因为可以由编译器推断得出,称为“类型推断” lambda若只需要一个参数时,参数的小括号可以省略。 lambda需要两个或以上的参数,多条执行语句,并且可以有返回值 当lambda体只有一条语句时,return与大括号若有,都可以省略 package new_characteristic.jdk8; import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.Comparator; import java.util.function.Consumer; @SuppressWarnings({"all"}) public class LambdaTest1 { // 语法格式1:无参,无返回值 @Test public void test1() { Runnable r1 = new Runnable() { @Override public void run() { // 抽象方法无参,无返回值 System.out.println("我爱Java"); } }; r1.run(); // 改写成Lambda表达式: Runnable r2 = () -> { System.out.println("我爱python"); }; r2.run(); } // 语法格式2:lambda需要一个参数,无返回值 @Test public void test2() { Consumer con1 = new Consumer() { @Override public void accept(String s) { System.out.println(s); } }; con1.accept("谎言和誓言的区别是什么?"); System.out.println("==========================="); Consumer con2 = (String s) -> { System.out.println(s); }; con2.accept("一个是听的人当真了,一个是说的人当真了"); } // 语法格式3:数据类型可以省略,因为可以由编译器推断得出,称为“类型推断” @Test public void test3() { Consumer con1 = (String s) -> { System.out.println(s); }; con1.accept("一个是听的人当真了,一个是说的人当真了"); System.out.println("==========================="); Consumer con2 = (s) -> { // 类型推断 System.out.println(s); }; con2.accept("一个是听的人当真了,一个是说的人当真了"); // 类型推断场景 ArrayList list = new ArrayList(); // 类型推断 int[] nums = {1, 2, 3}; // 类型推断 } // 语法格式4:lambda若只需要一个参数时,参数的小括号可以省略。 @Test public void test4() { Consumer con1 = (s) -> { // 类型推断 System.out.println(s); }; con1.accept("一个是听的人当真了,一个是说的人当真了"); System.out.println("==========================="); Consumer con2 = s -> { // 类型推断 System.out.println(s); }; con2.accept("一个是听的人当真了,一个是说的人当真了"); } // 语法格式5:lambda需要两个或以上的参数,多条执行语句,并且可以有返回值 @Test public void test5() { Comparator com1 = new Comparator() { @Override public int compare(Integer o1, Integer o2) { return o1.compareTo(o2); } }; System.out.println(com1.compare(13, 31)); System.out.println("==========================="); Comparator com2 = (o1, o2) -> { System.out.println(o1); System.out.println(o2); return o1.compareTo(o2); }; System.out.println(com2.compare(14, 41)); } // 语法格式6:当lambda体只有一条语句时,return与大括号若有,都可以省略 @Test public void test6() { Comparator com1 = (o1, o2) -> { return o1.compareTo(o2); }; System.out.println(com1.compare(14, 41)); System.out.println("============================"); Comparator com2 = (o1, o2) -> o1.compareTo(o2); System.out.println(com2.compare(25, 52)); } }

Lambda表达式总结:

使用Lambda表达式的前提:作为函数式接口的实现(只有一个方法)。

->左边:Lambda形参列表的参数类型可以省略(类型推断),如果Lambda形参列表只有一个参数,其一对()也可以省略。 ->右边:Lambda体应该使用一对{}包裹,如果Lambda体只有一条执行语句(可能是return语句),可以省略一对{}和return关键字({}和return关键字要么一起省略,要么都不省略) Lambda表达式本质:接口的对象 函数式接口

基本介绍:

只包含一个抽象方法的接口,称为函数式接口(FunctionalInterface)。

可以通过Lambda表达式来创建函数式接口的对象。(若Lambda表达式抛出一个受检异常(即:非运行时异常)),那么该异常需要在目标接口的抽象方法上进行声明。

可以在一个接口上使用@FunctionalInterface注解,这样能够检查该接口是否是一个函数式接口。同时javadoc也会包含一条声明来说明该接口是一个函数式接口。

在java.util.function包下定义了Java 8的大量函数式接口。

Java内置四大核心函数式接口:

函数式接口 参数类型 返回类型 用途 Consumer消费型接口 T void 对类型为T的对象应用操作,包含方法void accept(T t) Supplier供给型接口 无 T 返回类型为T的对象,包含方法T get() Function函数型接口 T R 对类型为T的对象应用操作,并返回结果,结果是R类型的对象,包含方法R apply(T t) Predicate指定型接口 T boolean 确定类型为T的对象是否满足某约束,并返回boolean值,包含方法:boolean test(T t) package new_characteristic.jdk8; import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; public class LambdaTest2 { @Test public void test1() { happy(500, new Consumer() { @Override public void accept(Double aDouble) { System.out.println("大学生每月开销:" + aDouble + "元"); } }); System.out.println("***********************************"); happy(600, aDouble -> System.out.println("大学生每月开销:" + aDouble + "元")); } /** * 传入参数给消费式接口中的方法 * * @param money 钱 * @param con 消费型接口 */ public void happy(double money, Consumer con) { con.accept(money); } @Test public void test2() { List list = Arrays.asList("北京", "南京", "天津", "普京", "东京"); List filtered1 = filterString(list, new Predicate() { @Override public boolean test(String s) { return s.contains("京"); } }); System.out.println(filtered1); System.out.println("***********************************"); List filtered2 = filterString(list, s -> s.contains("北")); System.out.println(filtered2); } /** * 根据给定的规则,过滤集合中的字符串,规则由Predicate中的test方法决定 * * @param list 需要过滤的字符串集合 * @param pre 函数型接口 * @return 返回过滤后的集合 */ public List filterString(List list, Predicate pre) { ArrayList filter = new ArrayList(); for (String s : list) { if (pre.test(s)) { filter.add(s); } } return filter; } }

方法引用与构造器引用 方法引用

基本介绍:

当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用。 方法引用可以看作是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法。 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致。针对情况1和情况2 格式:使用操作符::将类(或对象)与方法名分隔开来,常用情况: 对象::实例方法名(情况1) 类::静态方法名(情况2) 类:实例方法名(情况3) 方法引用,本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例,所以方法引用也是函数式接口的实例。 package new_characteristic.jdk8; import oop_medium_Exercise.homework05.Employee; import org.junit.jupiter.api.Test; import java.util.Comparator; import java.util.function.BiPredicate; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; /** * @Description: 测试方法引用的三种方式 对象::非静态方法名,类名::静态方法名,类名::非静态方法名 * @Author: Yao Xuan zhi * @Create: 2023-03-21, 14:51:16 * @IDE: IntelliJ IDEA */ public class MethodRefTest { // 情况一:对象::非静态方法名 /** * Consumer接口中的void accept(T t); * PrintStream中void println(T t)方法 */ @Test public void test1() { // lambda表达式 Consumer con1 = s -> System.out.println(s); con1.accept("Java8新特性 Lambda表达式"); System.out.println("*****************************"); Consumer con2 = System.out::println; con2.accept("Java8新特性 方法引用"); } /** * Employee中T getName() * Supplier 中T get(); */ @Test public void test2() { Employee emp1 = new Employee("Lww", 10000); Supplier sup1 = () -> emp1.getName(); System.out.println(sup1.get()); System.out.println("*****************************"); Employee emp2 = new Employee("Lww", 10000); Supplier sup2 = emp1::getName; System.out.println(sup2.get()); } // 情况二:类名::静态方法名 /** * Comparator中的int compare(T o1, T o2); * Integer中的int compare(T t1, T t2) */ @Test public void test3() { Comparator com1 = (t1, t2) -> Integer.compare(t1, t2); System.out.println(com1.compare(4, 3)); System.out.println("*****************************"); Comparator com2 = Integer::compare; System.out.println(com2.compare(4, 3)); } /** * Function接口中的 R apply(T t); * Math中的static long round(double a) */ @Test public void test4() { Function func = new Function() { @Override public Long apply(Double aDouble) { return Math.round(aDouble); } }; System.out.println(func.apply(1314.520)); System.out.println("*****************************"); Function func1 = d -> Math.round(d); System.out.println(func1.apply(3.14)); System.out.println("*****************************"); Function func2 = Math::round; System.out.println(func2.apply(520.1314)); } // 情况三:类名::实例方法名 /** * Comparator接口中的int compare(T o1, T o2); * String中的int T.compareTo(T t) */ @Test public void test5() { Comparator com1 = (s1, s2) -> s1.compareTo(s2); System.out.println(com1.compare("abc", "abd")); System.out.println("*****************************"); Comparator com2 = String::compareTo; System.out.println(com2.compare("abz", "abn")); } /** * BiPredicate中的boolean test(T t, U u); * String中的boolean t.equals(u) */ @Test public void test6() { BiPredicate pre1 = (s1, s2) -> s1.equals(s2); System.out.println(pre1.test("java", "java")); System.out.println("*****************************"); BiPredicate pre2 = String::equals; System.out.println(pre2.test("python", "python")); } /** * Function中的R apply(T t); * Employee中的String getName() */ @Test public void test7() { Function func1 = e -> e.getName(); System.out.println(func1.apply(new Employee("Lww", 12345))); System.out.println("*****************************"); Function func2 = Employee::getName; System.out.println(func2.apply(new Employee("Lww", 12345))); } } 构造器引用

和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致,抽象方法的返回类型即为构造器所属类的类型。

数组引用和构造器引用类似,把数组看成一个类。

package new_characteristic.jdk8; import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; public class ConstructorRef { //构造器引用和数组引用 /** * Supplier中的T get(); * Employee的空参构造器 */ @Test public void test1() { Supplier sup = new Supplier() { @Override public Employee get() { return new Employee(); } }; System.out.println(sup.get()); System.out.println("*************************"); Supplier sup1 = () -> new Employee(); System.out.println(sup1.get()); System.out.println("*************************"); Supplier sup2 = Employee::new; System.out.println(sup2.get()); } /** * Function中的 R apply(T t); */ @Test public void test2() { Function func1 = id -> new Employee(id); System.out.println(func1.apply(1)); System.out.println("*************************"); Function func2 = Employee::new; Employee employee = func2.apply(2); System.out.println(employee); } /** * BiFunction接口中的构造器 R apply(T t, U u) */ @Test public void test3() { BiFunction func1 = (id, name) -> new Employee(id, name); Employee yancy = func1.apply(3, "yancy"); System.out.println(yancy); System.out.println("*************************"); BiFunction func2 = Employee::new; Employee yxz = func2.apply(5, "yxz"); System.out.println(yxz); } /** * 数组引用 */ public void test4() { Function func1 = length -> new String[length]; String[] apply = func1.apply(5); System.out.println(Arrays.toString(apply)); System.out.println("*************************"); Function func2 = String[]::new; String[] apply2 = func2.apply(10); System.out.println(Arrays.toString(apply2)); } } Stream API 1、说明 Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API。 Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。 Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。 2、什么是Stream?

Stream 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,讲的是数据,而 Stream 是有关计算的,讲的是计算。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。

注意:

Stream 自己不会存储元素。 Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。 Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。即一旦执行终止操作,就执行中间操作链,并产生结果。 Stream一旦执行了终止操作,就不能再调用其它中间操作或终止操作了。 3、Stream的操作三个步骤

1- 创建 Stream 一个数据源(如:集合、数组),获取一个流

2- 中间操作 每次处理都会返回一个持有结果的新Stream,即中间操作的方法返回值仍然是Stream类型的对象。因此中间操作可以是个操作链,可对数据源的数据进行n次处理,但是在终结操作前,并不会真正执行。

3- 终止操作(终端操作) 终止操作的方法返回值类型就不再是Stream了,因此一旦执行终止操作,就结束整个Stream操作了。一旦执行终止操作,就执行中间操作链,最终产生结果并结束Stream。

3.1、创建Stream实例

方式一:通过集合

方式二:通过数组

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

static Stream stream(T[] array): 返回一个流 public static IntStream stream(int[] array) public static LongStream stream(long[] array) public static DoubleStream stream(double[] array) @Test public void test02(){ String[] arr = {"hello","world"}; Stream stream = Arrays.stream(arr); } @Test public void test03(){ int[] arr = {1,2,3,4,5}; IntStream stream = Arrays.stream(arr); }

方式三:通过Stream的of()

可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。

public static Stream of(T… values) : 返回一个流

@Test public void test04(){ Stream stream = Stream.of(1,2,3,4,5); stream.forEach(System.out::println); }

方式四:创建无限流(了解)

可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。 迭代:public static Stream iterate(final T seed, final UnaryOperator f) 生成:public static Stream generate(Supplier s)

// 方式四:创建无限流 @Test public void test05() { // 迭代 // public static Stream iterate(final T seed, final // UnaryOperator f) Stream stream = Stream.iterate(0, x -> x + 2); stream.limit(10).forEach(System.out::println); // 生成 // public static Stream generate(Supplier s) Stream stream1 = Stream.generate(Math::random); stream1.limit(10).forEach(System.out::println); } 3.2、一系列中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。

1-筛选与切片

方 法 描 述 filter(Predicatep) 接收 Lambda , 从流中排除某些元素 distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素 limit(long maxSize) 截断流,使其元素不超过给定数量 skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补

2-映 射

方法 描述 map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。 mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。 mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。 flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

3-排序

方法 描述 sorted() 产生一个新流,其中按自然顺序排序 sorted(Comparator com) 产生一个新流,其中按比较器顺序排序 package com.atguigu.stream; import org.junit.Test; import java.util.Arrays; import java.util.stream.Stream; public class StreamMiddleOperate { @Test public void test01(){ //1、创建Stream Stream stream = Stream.of(1,2,3,4,5,6); //2、加工处理 //过滤:filter(Predicate p) //把里面的偶数拿出来 /* * filter(Predicate p) * Predicate是函数式接口,抽象方法:boolean test(T t) */ stream = stream.filter(t -> t%2==0); //3、终结操作:例如:遍历 stream.forEach(System.out::println); } @Test public void test02(){ Stream.of(1,2,3,4,5,6) .filter(t -> t%2==0) .forEach(System.out::println); } @Test public void test03(){ Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5) .distinct() .forEach(System.out::println); } @Test public void test04(){ Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5) .limit(3) .forEach(System.out::println); } @Test public void test05(){ Stream.of(1,2,2,3,3,4,4,5,2,3,4,5,6,7) .distinct() //(1,2,3,4,5,6,7) .filter(t -> t%2!=0) //(1,3,5,7) .limit(3) .forEach(System.out::println); } @Test public void test06(){ Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5) .skip(5) .forEach(System.out::println); } @Test public void test07(){ Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5) .skip(5) .distinct() .filter(t -> t%3==0) .forEach(System.out::println); } @Test public void test08(){ long count = Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5) .distinct() .peek(System.out::println) //Consumer接口的抽象方法 void accept(T t) .count(); System.out.println("count="+count); } @Test public void test09(){ //希望能够找出前三个最大值,前三名最大的,不重复 Stream.of(11,2,39,4,54,6,2,22,3,3,4,54,54) .distinct() .sorted((t1,t2) -> -Integer.compare(t1, t2))//Comparator接口 int compare(T t1, T t2) .limit(3) .forEach(System.out::println); } @Test public void test10(){ Stream.of(1,2,3,4,5) .map(t -> t+=1)//Function接口抽象方法 R apply(T t) .forEach(System.out::println); } @Test public void test11(){ String[] arr = {"hello","world","java"}; Arrays.stream(arr) .map(t->t.toUpperCase()) .forEach(System.out::println); } @Test public void test12(){ String[] arr = {"hello","world","java"}; Arrays.stream(arr) .flatMap(t -> Stream.of(t.split("|")))//Function接口抽象方法 R apply(T t) 现在的R是一个Stream .forEach(System.out::println); } } 3.3、终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。

流进行了终止操作后,不能再次使用。

1-匹配与查找

方法 描述 allMatch(Predicate p) 检查是否匹配所有元素 anyMatch(Predicate p) 检查是否至少匹配一个元素 noneMatch(Predicate p) 检查是否没有匹配所有元素 findFirst() 返回第一个元素 findAny() 返回当前流中的任意元素 count() 返回流中元素总数 max(Comparator c) 返回流中最大值 min(Comparator c) 返回流中最小值 forEach(Consumer c) 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)

2-归约

方法 描述 reduce(T identity, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 T reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional

备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。

3-收集

方 法 描 述 collect(Collector c) 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法

Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。

另外,Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

方法 返回类型 作用 toList Collector 把流中元素收集到List toSet Collector 把流中元素收集到Set toCollection Collector 把流中元素收集到创建的集合 counting Collector 计算流中元素的个数 summingInt Collector 对流中元素的整数属性求和 averagingInt Collector 计算流中元素Integer属性的平均值 summarizingInt Collector 收集流中Integer属性的统计值。如:平均值 joining Collector 连接流中每个字符串 maxBy Collector 根据比较器选择最大值 minBy Collector 根据比较器选择最小值 reducing Collector 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值 collectingAndThen Collector 包裹另一个收集器,对其结果转换函数 groupingBy Collector 根据某属性值对流分组,属性为K,结果为V partitioningBy Collector 根据true或false进行分区 package com.atguigu.stream; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.Test; public class StreamEndding { @Test public void test01(){ Stream.of(1,2,3,4,5) .forEach(System.out::println); } @Test public void test02(){ long count = Stream.of(1,2,3,4,5) .count(); System.out.println("count = " + count); } @Test public void test03(){ boolean result = Stream.of(1,3,5,7,9) .allMatch(t -> t%2!=0); System.out.println(result); } @Test public void test04(){ boolean result = Stream.of(1,3,5,7,9) .anyMatch(t -> t%2==0); System.out.println(result); } @Test public void test05(){ Optional opt = Stream.of(1,3,5,7,9).findFirst(); System.out.println(opt); } @Test public void test06(){ Optional opt = Stream.of(1,2,3,4,5,7,9) .filter(t -> t%3==0) .findFirst(); System.out.println(opt); } @Test public void test07(){ Optional opt = Stream.of(1,2,4,5,7,8) .filter(t -> t%3==0) .findFirst(); System.out.println(opt); } @Test public void test08(){ Optional max = Stream.of(1,2,4,5,7,8) .max((t1,t2) -> Integer.compare(t1, t2)); System.out.println(max); } @Test public void test09(){ Integer reduce = Stream.of(1,2,4,5,7,8) .reduce(0, (t1,t2) -> t1+t2);//BinaryOperator接口 T apply(T t1, T t2) System.out.println(reduce); } @Test public void test10(){ Optional max = Stream.of(1,2,4,5,7,8) .reduce((t1,t2) -> t1>t2?t1:t2);//BinaryOperator接口 T apply(T t1, T t2) System.out.println(max); } @Test public void test11(){ List list = Stream.of(1,2,4,5,7,8) .filter(t -> t%2==0) .collect(Collectors.toList()); System.out.println(list); } } 4、Java9新增API

新增1:Stream实例化方法

ofNullable()的使用:

Java 8 中 Stream 不能完全为null,否则会报空指针异常。而 Java 9 中的 ofNullable 方法允许我们创建一个单元素 Stream,可以包含一个非空元素,也可以创建一个空 Stream。

//报NullPointerException //Stream stream1 = Stream.of(null); //System.out.println(stream1.count()); //不报异常,允许通过 Stream stringStream = Stream.of("AA", "BB", null); System.out.println(stringStream.count());//3 //不报异常,允许通过 List list = new ArrayList(); list.add("AA"); list.add(null); System.out.println(list.stream().count());//2 //ofNullable():允许值为null Stream stream1 = Stream.ofNullable(null); System.out.println(stream1.count());//0 Stream stream = Stream.ofNullable("hello world"); System.out.println(stream.count());//1

iterator()重载的使用:

//原来的控制终止方式: Stream.iterate(1,i -> i + 1).limit(10).forEach(System.out::println); //现在的终止方式: Stream.iterate(1,i -> i < 100,i -> i + 1).forEach(System.out::println); 接口增强

待补充...

Optional类

待补充...

并行流

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行流可以很大程度上提高程序的执行效率。

Java8中将并行进行了优化,StreamAPI可以声明性的通过parallel()与sequential()在并行流与顺序流之间进行切换。

串行流

待补充...

Java 9新特性

待补充...

Java 10新特性

待补充...

Java 11新特性

待补充...

0-9 ↩︎

0-9 ↩︎



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3